——————这篇文章旨在提出一种简单方便,易于理解时空复杂度低且风格统一的二叉树非递归遍历方法。

                               从二叉树先序遍历开始

  二叉树的先序遍历(非递归)相比中后序是最少花哨、最统一的。一般来说先序遍历的代码如下:

void preorder(TNode* p,FUNC Func)
{TNode* stack[maxsize];int itop = -1;if(p == NULL)return ;stack[++itop] = p;while(itop != -1){p = stack[itop--];Func(p);if(p->rchild)stack[++itop] = p->rchild;if(p->lchild)stack[++itop] = p->lchild;}
}

  代码很短。先序的思想非常简单,从代码中可以看出:先将根节点压入栈中,从栈中取元素,遍历,然后按右左节点的顺序将元素压入栈中(如果存在的话),如此循环,直到栈空。

  当然这么说的话是很官方了,通俗点儿来讲,其实就是每进入一个节点就先访问本节点,然后左边有节点的话往走左,左边没有往右走,或者左边走完往右走,直到走完为止。这种思想非常通俗易懂,其代码效率也高,所以被广泛采用也不奇怪。其时间复杂度是每个节点访问一遍,进出栈一遍,应为O(3*n),效率很高了。其空间复杂度由于是每出栈一个节点,则其左右节点皆进栈(如果存在的话),考虑最复杂的情况,即二叉树为完全二叉树或满二叉树,则其空间复杂度为O(deepth),其中deepth为树的深度,也是很不错的表现。

  不幸的是这种简洁明了的思想风格在中序遍历中并没有延续下去。中序遍历的思想风格相对于先序遍历变得非常奇怪,从其最经典最被广泛采用的的中序遍历代码中可见一斑:

void midorder(TNode* p,FUNC Func)
{TNode* stack[maxsize];int itop = -1;while(itop != -1||p){while(p!=NULL){stack[++itop] = p;p = p->lchild;}p = stack[itop--];Func(p);p = p->rchild;}
}

  代码依然不长。但其思想似乎变得奇怪了起来:从根节点开始不断地将p置入栈中,p不断地进入左子树,直到p所指向的树为空为止,然后从栈中取元素赋给p,访问p,然后进入右子树,如此循环,直到栈为空且p无效时跳出,遍历完成。

  当然还是那句话,这么说的话是很官方了。通俗点儿来讲,就是我从根节点开始一直往左走(把我走过的点都存到栈里),走到走不动了,就访问本节点,再往右边走,然后再往左走一直到走不动。。。如此循环,走完所有的节点为止(此处是因为节点全部来自于p和p的左子树以及栈中,所以当p无效且栈空的时候,自然就走完所有的节点了)。是不是感觉到和先序遍历完全不同的风格?当然,风格这种之后再分析,先来看它的时空复杂度:从代码中可以看出,每个节点都会进栈一次出栈一次,被访问一次,其时间复杂度大致为O(3*n)。空间复杂度上,算法每到一个节点,都会往左走到头为止,考虑最复杂的情况,则其空间复杂度为树的深度O(deepth)。这也是很不错的表现了。

  在分析完复杂度之后,转头来看看思想风格:先序的思想风格简单明了,站在当前的节点上,我不需要知道这是叶节点还是根节点,我只需要不断的访问,然后压右走左就可以。但是这种简单明了来到中序之后荡然无存,中序开始站在一种全局的角度进行观察,我开始需要知道我走在哪个节点上了,我开始需要关注整体的遍历顺序了,而这种中序遍历的遍历顺序,在代码中体现的淋漓尽致:”不断往左走,走到头访问,然后往右走。。。“,与之相对比,显然先序并不需要这些考量,它不需要知道先序的遍历顺序是什么样的,它只需要不断的站在当前节点,我该干啥干啥,就OK了。这种思想风格虽然对于新手来说很难写出来,但是毕竟时空复杂度尚可,所以也还可接受。

  但后序遍历扭曲的就不仅仅是思想风格了,与之相比还更高的时空复杂度。而后序也因很多算法较高的难以接受的时空复杂度,而产生了五花八门风格各异的算法,也算是百花齐放了。其中算法参差不齐,有优有劣,具体就不再列举,这里来说一说这篇的正题。

  

                      风格统一、时空复杂度低、思想简单明了的二叉树非递归遍历

  回到文章开头的地方,我们开篇就说了,先序遍历的思想是最简单明了的。显然,如果能按先序的思想进行中序后序遍历,一切就会变得非常简单。

  所以我们这里来尝试一下用先序的思想遍历中序。先序是一种基于当前节点,不需要知道我是不是走到头了,我是不是根节点叶节点,我只需要不断的遍历本身,压右进左即可。可是以中序为例,中序的时候显然不是这样,我们需要先遍历完左子树,然后在遍历本身在向右走。在这种情况下,我们必须要知道在当前节点我们是否已经遍历完成左子树。那么我们现在就是要求遍历完成左子树的结论,但是要求结果,和数学题一样,就必须要明确我们都有哪些条件,要知道我们有哪些条件,就必须要还原利用先序遍历思想遍历中序时候的当前情况,才能从中总结出来条件。

  那么我们还原一下:我们从根节点开始,将本节点压入栈中,向左走,进入新节点。。。当我们左子树遍历完成,要回到根节点,此时我们需要拿到栈顶元素(这个元素一定是根节点),遍历,然后向右走。。。有没有发现什么?压栈压的是本身节点,这决定了当你从左节点回到父节点时,一定有栈顶元素的左子树等于当前节点!而这正是我们所要求的那个左子树遍历完成的条件!

  由此我们就可以总结出来这种中序遍历的思想了,设置一个标志位left,初值为零,代表没走过左子树的状态,当进入一个新节点时,我们设置left=0,表示当前节点没遍历过左子树,当从左子树回到父节点时,设置left=1,表示当前节点(父节点)遍历过了左子树。

  代码如下:

void midorder(TNode* p,FUNC Func)
{int left = 0;TNode* stack[maxsize];int itop = -1;while(p){if(p->lchild&&!left){stack[++itop] = p;p = p->lchild;left = 0;}else{Func(p);if(p->rchild){p = p->rchild;left = 0;}else{left = 1;if(itop != -1)p = stack[itop--];else{p = NULL;}}}}
}

  同理,推广到后序遍历上,也很容易。我们可以设置两个标志位,一个left一个right,作用同上。代码如下:

 

void lastorder(TNode* p,FUNC Func)
{TNode* stack[maxsize];int left = 0,right = 0,itop = -1;while(p){if(!left && p->lchild ){stack[++itop] = p;p = p->lchild;left = right = 0;}else if(!right && p->rchild){stack[++itop] = p;p = p->rchild;left = right = 0;}else{Func(p);if(itop != -1){if(p == stack[itop]->lchild){left = 1;right = 0;}else {left = right = 1;}p = stack[itop--];}else{p = NULL;}}}
}

  由于三者代码思想都是相同的,所以我这里只挑后序的遍历方法来讲讲就可以:从根节点开始,如果left,right都为0,则说明本节点左右都没走过,那么先走左;如果我们要从左节点回到根节点,那么设置left为1,right为0,表示左节点走过,但右节点没走过;如果我们要从右节点回到根节点(此时判断右节点是否与栈顶元素的右子树相等),那么设置left,right都为1,表示此根节点左右节点都走过,只需要遍历本节点就可以了;如果我们要进入左节点或者右节点,那么就设置left,right都为0,因为进入新节点则其左右都没走过。

  这里代码是写的不太好的,因为实在懒得优化了。但其思想是非常好的。时间复杂度按后序来算,则其每个节点要走三次,大致为O(3*n),其实也是O(n),与前中序差不多,很好的表现了;空间复杂度按后序来算,应为树的深度O(deepth),也与前中序差不多的表现了。

  这个算法还有一个好的地方在哪里呢?它的可扩展性,它可以不仅仅用于二叉树,也可以用于n叉树的m序遍历,只需要给定m个标志位即可,而其思想与代码的风格仍是大同小异,这里就不再赘述,有兴趣的读者可以自己去实现,也是很有意思的一件事。

转载于:https://www.cnblogs.com/FWFC/p/8708511.html

漫谈二叉树遍历(非递归)相关推荐

  1. 刷题:二叉树的非递归遍历方式

    二叉树的非递归的遍历方式 上篇博客记录了二叉树的递归遍历方式以及根据二叉树的遍历结果还原二叉树的内容. 本篇博客记录二叉树的非递归的遍历方式. 二叉树的非递归遍历需要借助栈来实现,而且三种遍历的方式的 ...

  2. 二叉树的非递归遍历(c/c++)

    由于递归算法相对于非递归算法来说效率通常都会更低,递归算法会有更多的资源需要压栈和出栈操作(不仅仅是参数,还有函数地址等)由于编译器对附加的一些栈保护机制会导致递归执行的更加低效,使用循环代替递归算法 ...

  3. 创建的二叉树后续非递归遍历结果为_一入递归深似海,从此offer是路人

    前言 今天我们来总结二叉树的前中后序以及层次遍历的递归与非递归的写法.我们都知道二叉树遍历的递归写法很简单,但是面试的时候面试官往往考察的不是我们递归的写法,他们满怀期待你写出非递归的解法,而当你只会 ...

  4. 二叉树的非递归遍历(统一的模板)

    二叉树的非递归遍历 前言 树的存储结构 先序遍历 先序的递归遍历 先序的非递归遍历 中序遍历 中序的递归遍历 中序遍历的非递归算法 后序遍历 后序的递归遍历 后序的非递归遍历 层次遍历 层次遍历获得每 ...

  5. 数据结构-二叉树的非递归遍历

    前面的章节我们实现了二叉树最基本的遍历方式:递归遍历,代码是如此的简洁:辣么我们为什么还要去学习二叉树的非递归遍历方式呢?众所周知,递归优点是将可以将复杂的问题简单化即大问题拆分成一个个小问题,那么它 ...

  6. c语言以顺序结构存储的二叉树的非递归遍历,C语言二叉树的非递归遍历实例分析...

    本文以实例形式讲述了C语言实现二叉树的非递归遍历方法.是数据结构与算法设计中常用的技巧.分享给大家供大家参考.具体方法如下: 先序遍历: void preOrder(Node *p) //非递归 { ...

  7. 二叉树前序、中序、后序遍历非递归写法的透彻解析

    前言 在前两篇文章二叉树和二叉搜索树中已经涉及到了二叉树的三种遍历.递归写法,只要理解思想,几行代码.可是非递归写法却很不容易.这里特地总结下,透彻解析它们的非递归写法.其中,中序遍历的非递归写法最简 ...

  8. 树:二叉树的非递归遍历算法

    二叉树的递归遍历 二叉树的递归遍历算法,写法很简单,比如说前序遍历树,如下: //前序遍历 void PreOrderTraverse(BiTree tree) {if (NULL != tree){ ...

  9. 6-9 二叉树的非递归遍历 (20 分)

    ** 6-9 二叉树的非递归遍历 (20 分) ** 本题要求用非递归的方法实现对给定二叉树的 3 种遍历. 函数接口定义: void InorderTraversal( BinTree BT ); ...

  10. 二叉树后序遍历_二叉树后序遍历非递归实现

    二叉树的后序遍历非递归实现是三种遍历实现里面最复杂的一种了. 后序遍历的顺序是左节点-右节点-根节点,因为二叉树每个节点只有指向子节点的指针而没有指向父节点的指针,因此我们需要一个额外的变量来记录是否 ...

最新文章

  1. java 线程 Thread 使用介绍,包含wait(),notifyAll() 等函数使用介绍
  2. google 用新的tab打开网页
  3. linux shell脚本的执行方式与区别
  4. java object 引用类型_java中的四种引用类型
  5. Python中操作MySQL/Oracle
  6. CG CTF WEB md5 collision
  7. 修改autor后面邮箱_如何修改LOL手游昵称
  8. jgroups_JGroups:无需额外基础架构的领导人选举
  9. 看视频学编程之WinForm
  10. cmd、start命令中有空格的解决方法
  11. 某盘视频网页播放视频修改播放速度代码
  12. 微信语音转mp3 php,微信语音amr转mp3
  13. 马尔科夫不等式与切比雪夫不等式
  14. C语言 计算总分和平均数
  15. 带你入门Java网络爬虫
  16. Python 打新股,我建议你这么来操作!
  17. swiper点击左右失效
  18. Python爬取【京东商城】商品信息实例(末尾有关于价格的爬取)
  19. 解决win10连接:L2TP连接尝试失败,因为安全层在初始化与远程计算机的...
  20. 启动u盘的制作与系统重装

热门文章

  1. sql server 经典SQL——分组统计
  2. 公开课视频-《第04章 部署-Microsoft-服务器虚拟化-Hyper-V 2012 R2》
  3. 移动分发端 基础统计指标经典业务代码节选--留存用户统计
  4. Linux RAR 安装和使用详细说明
  5. [置顶]使用 maven 插件 maven-shade-plugin 对可执行 java 工程及其全部依赖 jar 进行打包...
  6. 新手初学Regular Expression正则表达式--快速入门
  7. Nagios+mutt+msmtp 无法发送邮件的问题!
  8. 利用XRDP远程登陆linux系统
  9. Hibernate 配置 p6spy 显示完整 sql 语句
  10. burpsuite 设置https_新手教程:如何使用Burpsuite抓取手机APP的HTTPS数据