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

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

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

三种递归遍历

//前序遍历
void preorder(TreeNode *root, vector<int> &path) { if(root != NULL) { path.push_back(root->val); preorder(root->left, path); preorder(root->right, path); } }
//中序遍历
void inorder(TreeNode *root, vector<int> &path) { if(root != NULL) { inorder(root->left, path); path.push_back(root->val); inorder(root->right, path); } }
//后续遍历
void postorder(TreeNode *root, vector<int> &path) { if(root != NULL) { postorder(root->left, path); postorder(root->right, path); path.push_back(root->val); } }

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

教科书上的非递归遍历

//非递归前序遍历
void preorderTraversal(TreeNode *root, vector<int> &path) { stack<TreeNode *> 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; } } }
//非递归中序遍历
void inorderTraversal(TreeNode *root, vector<int> &path) { stack<TreeNode *> 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<int> &path)
{stack<TempNode *> s;TreeNode *p = root;TempNode *temp;while(p != NULL || !s.empty()) { while(p != NULL) //沿左子树一直往下搜索,直至出现没有左子树的结点 { TreeNode *tempNode = new TreeNode; 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; } } } }

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

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

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

//更简单的非递归前序遍历
void preorderTraversalNew(TreeNode *root, vector<int> &path) { stack< pair<TreeNode *, bool> > s; s.push(make_pair(root, false)); bool visited; 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)); } } }
//更简单的非递归中序遍历
void inorderTraversalNew(TreeNode *root, vector<int> &path) { stack< pair<TreeNode *, bool> > s; s.push(make_pair(root, false)); bool visited; 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)); } } }
//更简单的非递归后序遍历
void postorderTraversalNew(TreeNode *root, vector<int> &path) { stack< pair<TreeNode *, bool> > s; s.push(make_pair(root, false)); bool visited; 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<int> &path)
{stack<TreeNode *> 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); } } }

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

转自http://www.jianshu.com/p/4db970d8ddc1

posted on 2016-01-23 19:00 kernel_main 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/kernel0815/p/5153813.html

【转】更简单的非递归遍历二叉树的方法相关推荐

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

    解决二叉树的很多问题的方案都是基于对二叉树的遍历.遍历二叉树的前序,中序,后序三大方法算是计算机科班学生必写代码了.其递归遍历是人人都能信手拈来,可是在手生时写出非递归遍历恐非易事.正因为并非易事,所 ...

  2. 更简单的非递归遍历二叉树

    解决二叉树的很多问题的方案都是基于对二叉树的遍历.遍历二叉树的前序,中序,后序三大方法算是计算机科班学生必写代码了.其递归遍历是人人都能信手拈来,可是在手生时写出非递归遍历恐非易事.正因为并非易事,所 ...

  3. 非递归遍历二叉树实现和理解

    非递归遍历二叉树 1.前言 ​ 总所周知,二叉树的遍历分为先序遍历.中序遍历和后序遍历.遍历的顺序不同,则结果不同.而遍历方法也分递归和非递归.而二者的复杂度相同:时间复杂度为O(nlgn),空间复杂 ...

  4. C++实现递归,非递归遍历二叉树(前序,中序,后序)

    初学二叉树那会儿,始终掌握不好二叉树的遍历方法,更认为非递归遍历晦涩难懂没有掌握的意义.实际上非递归的遍历方法很有用处,由于每次递归都需要将函数的信息入栈,当递归层数太深很容易就导致栈溢出,所以这个时 ...

  5. 中序非递归遍历二叉树

    二叉树的递归算法虽然简单,但是会导致时间复杂度比较高,下面给大家带来用栈实现的二叉树非递归算法 首先定义好二叉树,和元素类型为二叉树的栈 typedef struct BiTNode{TElemTyp ...

  6. 数据结构专题 | 先序非递归遍历二叉树

    先序非递归遍历二叉树,主要是利用了栈的先进后出原理,用一个栈即可实现该算法,下面我们一起来看一下如何来实现吧 目录 先序建立二叉树 先序递归遍历二叉树 先序非递归遍历二叉树 先序建立二叉树 在进行先序 ...

  7. 非递归遍历二叉树(算法导论第三版第十章10.4-5)

    非递归遍历二叉树(算法导论第三版第十章10.4-5) template<typename T> void TraverseBinaryTreeNonRecursive(BinaryTree ...

  8. 非递归遍历二叉树(后序遍历)

    非递归遍历二叉树(后序遍历) 在二叉树的遍历中,分为递归遍历与非递归遍历.非递归遍历的执行效率较高,时间复杂度小,因此采用非递归遍历有利于提高代码运行效率. //后序遍历非递归实现 void Post ...

  9. 数据结构_非递归遍历二叉树(C语言)

    数据结构总目录 非递归遍历二叉树 1. 图文解析 对于链式二叉树,如果要用非递归的方式进行前.中.后序遍历,则需要借助一个栈实现,而层序遍历则需要借助队列来实现. 构建如下二叉树: 非递归先序遍历 ( ...

最新文章

  1. LeetCode-剑指 Offer 50. 第一个只出现一次的字符
  2. Hibernate 之单向多对一映射及其衍生问题
  3. c++ 4.变量名规则
  4. 在网上看到一个故事,我觉得挺感人
  5. java中类型转换的造型_Java总结篇系列:类型转换/造型
  6. 老李推荐:第8章2节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-解析处理命令行参数 2...
  7. 无代码调整聚类热图分支顺序
  8. Apocalypse Someday(POJ-3208)
  9. c语言 mysql 查询数字_c语言mysql查询数据库
  10. 3.4.2 - Operators 3.4.3 division and truncation
  11. 实施IT运维管理 循序渐进
  12. centos 网卡状态
  13. mysql --force 无效_【技能库】--mysql 索引失效 force index也失效-- 原因解决方案(256)...
  14. JVM相关知识——内存分布和垃圾回收机制
  15. OpenSSL之X509证书用法
  16. 碳排放权交易管理办法即将施行,你知道火电厂的碳排放是怎么算出来的吗?
  17. 我的世界梦之边缘5服务器在维护吗,我的世界梦之边缘V 第一个包含动漫CG剧情的RPG服务器...
  18. Pygame实战之外星人入侵NO.9——消灭外星人
  19. Mac苹果电脑开不了机怎么办,该怎么修复
  20. FTDI 2232H GPIO设置 NAND Read

热门文章

  1. Spring Boot实现简单的用户权限管理(超详细版)
  2. c# 访问hbase_大数据技术 windows下C#通过Thrift操作HBase
  3. xp mysql字符集与乱码_mysql字符集(GBK、GB2312、UTF8)与中文乱码的原因及解决
  4. OpenKruise v1.0:云原生应用自动化达到新的高峰
  5. 从基础设施到云原生应用,全方位解读阿里云原生新锐开源项目
  6. 从单体迈向 Serverless 的避坑指南
  7. 阿里云叔同:以容器为代表的云原生技术,已经成为释放云价值的最短路径
  8. Kubernetes 入门必备云原生发展简史
  9. 实验Matlab数值运算,MATLAB数值实验一(数据的插值运算及其应用完整版
  10. python线性回归预测pm2.5_线性回归--PM2.5预测--李宏毅机器学习