更简单的非递归遍历二叉树的方法

//www.greatytc.com/p/49c8cfd07410

解决二叉树的很多问题的方案都是基于对二叉树的遍历。遍历二叉树的前序,中序,后序三大方法算是计算机科班学生必写代码了。其递归遍历是人人都能信手拈来,可是在手生时写出非递归遍历恐非易事。正因为并非易事,所以网上出现无数的介绍二叉树非递归遍历方法的文章。可是大家需要的真是那些非递归遍历代码和讲述吗?代码早在学数据结构时就看懂了,理解了,可为什么我们一而再再而三地忘记非递归遍历方法,却始终记住了递归遍历方法?

三种递归遍历对遍历的描述,思路非常简洁,最重要的是三种方法完全统一,大大减轻了我们理解的负担。而我们常接触到那三种非递归遍历方法,除了都使用栈,具体实现各有差异,导致了理解的模糊。本文给出了一种统一的三大非递归遍历的实现思想。

三种递归遍历

//前序遍历voidpreorder(TreeNode *root,vector &path){if(root !=NULL)    {        path.push_back(root->val);        preorder(root->left, path);        preorder(root->right, path);    }}

//中序遍历voidinorder(TreeNode *root,vector &path){if(root !=NULL)    {        inorder(root->left, path);        path.push_back(root->val);        inorder(root->right, path);    }}

//后续遍历voidpostorder(TreeNode *root,vector &path){if(root !=NULL)    {        postorder(root->left, path);        postorder(root->right, path);        path.push_back(root->val);    }}

由上可见,递归的算法实现思路和代码风格非常统一,关于“递归”的理解可见我的《人脑理解递归》

教科书上的非递归遍历

//非递归前序遍历voidpreorderTraversal(TreeNode *root,vector &path){stack s;    TreeNode *p = root;while(p !=NULL|| !s.empty())    {while(p !=NULL)        {            path.push_back(p->val);            s.push(p);            p = p->left;        }if(!s.empty())        {            p = s.top();            s.pop();            p = p->right;        }    }}

//非递归中序遍历voidinorderTraversal(TreeNode *root,vector &path){stack s;    TreeNode *p = root;while(p !=NULL|| !s.empty())    {while(p !=NULL)        {            s.push(p);            p = p->left;        }if(!s.empty())        {            p = s.top();            path.push_back(p->val);            s.pop();            p = p->right;        }    }}

//非递归后序遍历-迭代void postorderTraversal(TreeNode *root, vector &path){    stack s;    TreeNode *p = root;    TempNode *temp;while(p !=NULL|| !s.empty())    {while(p !=NULL)//沿左子树一直往下搜索,直至出现没有左子树的结点{            TreeNode *tempNode =newTreeNode;            tempNode->btnode = p;            tempNode->isFirst =true;            s.push(tempNode);            p = p->left;        }if(!s.empty())        {            temp = s.top();            s.pop();if(temp->isFirst ==true)//表示是第一次出现在栈顶{                temp->isFirst =false;                s.push(temp);                p = temp->btnode->right;            }else//第二次出现在栈顶{                path.push_back(temp->btnode->val);                p =NULL;            }        }    }}

看了上面教科书的三种非递归遍历方法,不难发现,后序遍历的实现的复杂程度明显高于前序遍历和中序遍历,前序遍历和中序遍历看似实现风格一样,但是实际上前者是在指针迭代时访问结点值,后者是在栈顶访问结点值,实现思路也是有本质区别的。而这三种方法最大的缺点就是都使用嵌套循环,大大增加了理解的复杂度。

更简单的非递归遍历二叉树的方法

这里我给出统一的实现思路和代码风格的方法,完成对二叉树的三种非递归遍历。

//更简单的非递归前序遍历voidpreorderTraversalNew(TreeNode *root,vector &path){stack< pair > s;    s.push(make_pair(root,false));boolvisited;while(!s.empty())    {        root = s.top().first;        visited = s.top().second;        s.pop();if(root ==NULL)continue;if(visited)        {            path.push_back(root->val);        }else{            s.push(make_pair(root->right,false));            s.push(make_pair(root->left,false));            s.push(make_pair(root,true));        }    }}

//更简单的非递归中序遍历voidinorderTraversalNew(TreeNode *root,vector &path){stack< pair > s;    s.push(make_pair(root,false));boolvisited;while(!s.empty())    {        root = s.top().first;        visited = s.top().second;        s.pop();if(root ==NULL)continue;if(visited)        {            path.push_back(root->val);        }else{            s.push(make_pair(root->right,false));            s.push(make_pair(root,true));            s.push(make_pair(root->left,false));        }    }}

//更简单的非递归后序遍历voidpostorderTraversalNew(TreeNode *root,vector &path){stack< pair > s;    s.push(make_pair(root,false));boolvisited;while(!s.empty())    {        root = s.top().first;        visited = s.top().second;        s.pop();if(root ==NULL)continue;if(visited)        {            path.push_back(root->val);        }else{            s.push(make_pair(root,true));            s.push(make_pair(root->right,false));            s.push(make_pair(root->left,false));        }    }}

以上三种遍历实现代码行数一模一样,如同递归遍历一样,只有三行核心代码的先后顺序有区别。为什么能产生这样的效果?下面我将会介绍。

有重合元素的局部有序一定能导致整体有序

这就是我得以统一三种更简单的非递归遍历方法的基本思想:有重合元素的局部有序一定能导致整体有序

如下这段序列,局部2 3 4和局部1 2 3都是有序的,但是不能由此保证整体有序。

而下面这段序列,局部2 3 4,4 5 6,6 8 10都是有序的,而且相邻局部都有一个重合元素,所以保证了序列整体也是有序的。

应用于二叉树

基于这种思想,我就构思三种非递归遍历的统一思想:不管是前序,中序,后序,只要我能保证对每个结点而言,该结点,其左子结点,其右子结点都满足以前序/中序/后序的访问顺序,整个二叉树的这种三结点局部有序一定能保证整体以前序/中序/后序访问,因为相邻的局部必有重合的结点,即一个局部的“根”结点是另外一个局部的“子”结点。

如下图,对二叉树而言,将每个框内结点集都看做一个局部,那么局部有A,A B C,B D E,D,E,C F,F,并且可以发现每个结点元素都是相邻的两个局部的重合结点。发觉这个是非常关键的,因为知道了重合结点,就可以对一个局部排好序后,通过取出一个重合结点过渡到与之相邻的局部进行新的局部排序。我们可以用栈来保证局部的顺序(排在顺序前面的后入栈,排在后面的先入栈,保证这个局部元素出栈的顺序一定正确),然后通过栈顶元素(重合元素)过渡到对新局部的排序,对新局部的排序会导致该重合结点再次入栈,所以当栈顶出现已完成过渡使命的结点时,就可以彻底出栈输出了(而这个输出可以保证该结点在它过渡的那个局部一定就是排在最前面的),而新栈顶元素将会继续完成新局部的过渡。当所有结点都完成了过渡使命时,就全部出栈了,这时我敢保证所有局部元素都是有序出栈,而相邻局部必有重合元素则保证了整体的输出一定是有序的。这种思想的好处是将算法与顺序分离,定义何种顺序并不影响算法,算法只做这么一件事:将栈顶元素取出,使以此元素为“根”结点的局部有序入栈,但若此前已通过该结点将其局部入栈,则直接出栈输出即可

从实现的程序中可以看到:三种非递归遍历唯一不同的就是局部入栈的三行代码的先后顺序。所以不管是根->左->右,左->根->右,左->右->根,甚至是根->右->左,右->根->左,右->左->根定义的新顺序,算法实现都无变化,除了改变局部入栈顺序。

值得一提的是,对于前序遍历,大家可能发现取出一个栈顶元素,使其局部前序入栈后,栈顶元素依然是此元素,接着就要出栈输出了,所以使其随局部入栈是没有必要的,其代码就可以简化为下面的形式。

void preorderTraversalNew(TreeNode*root, vector &path)

{

stack s;s.push(root);while(!s.empty()){        root = s.top();s.pop();if(root== NULL){            continue;}        else        {            path.push_back(root->val);s.push(root->right);s.push(root->left);}    }}

这就是我要介绍的一种更简单的非递归遍历二叉树的方法。

文/紫松(简书作者)

原文链接://www.greatytc.com/p/49c8cfd07410

著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容