非递归前序、中序遍历代码推演出后序遍历代码(极其透彻)

news/2024/11/20 8:29:42/

一、前言

众所周知,二叉树的遍历方式有三种:前序遍历、中序遍历和后序遍历。

🍌 前序遍历:首先访问根节点,然后递归遍历左子树,最后递归遍历右子树。

🍌 中序遍历:首先递归遍历左子树,然后访问根节点,最后递归遍历右子树。

🍌 后序遍历:首先递归遍历左子树,然后递归遍历右子树,最后访问根节点。

上面三种遍历方式每一种又存在两种实现形式:递归和非递归。

 🍓 递归遍历二叉树:在函数内通过调用自身来完成遍历的一种方式,本质是利用函数调用栈来实现的,每次递归都要建立一个新的函数栈帧,效率较低,空间复杂度较大,更适合深度优先搜索。

🍓 非递归遍历二叉树:利用循环来遍历二叉树,用自己定义的栈来实现的,更适合广度优先搜索。

深度优先搜索(DFS):从根节点出发,先搜索左子树,再搜索右子树的遍历方式。这种遍历方式可以使用递归来实现。

广度优先搜索(BFS):从根节点开始,按层次遍历二叉树的方式。这种遍历方式通常不使用递归来实现,而是使用队列来实现。


二、非递归前序和中序遍历算法

下面是非递归前序和中序遍历对比(代码在下方可拷贝,代码含义详见另一篇文章戳),我们可以看出两种非递归遍历方式只是访问跟根节点的位置不同,这显然是废话!仔细看下,有没有什么新发现?如果实在是没有那就继续往下看吧。

非递归前序和中序遍历对比
// 先序非递归遍历
void pre_order_traverse_none(BiTree bitree)
{// 思路:先序顺序为:根节点->左子树->右子树// 当遍历完左子树之后找不到右子树// 解决方法:记住根节点stack<BiTree> stk;BiTree p = bitree;// 当前节点不为空,或者栈不为空while(p || !stk.empty()){if(p)// 根节点不为空{cout << p->data<< ",";stk.push(p);// 根指针入栈p = p->lchild;// 遍历左子树}else// 栈不为空{p = stk.top();// 获取根节点stk.pop();// 弹出根节点p = p->rchild;// 访问右子树}}}// 中序非递归遍历
void in_order_traverse_none(BiTree &bitree)
{// 思路:中序顺序为:左子树->根节点->右子树// 当遍历完左子树之后找不到根节点// 解决方法:记住根节点BiTree p = bitree;stack<BiTree> stk;while(p || !stk.empty()){if(p)// 如果根节点非空,压栈,记录根节点{stk.push(p);p = p->lchild;// 首先遍历左子树}else// 栈非空,弹出根节点{p = stk.top();cout << p->data<<" " ;stk.pop();p = p->rchild;// 访问右子树}}
}

从上面的代码对比中我们可以看出,在入栈时进行了根节点访问,访问方式即为非递归前序遍历,在弹栈时进行根节点访问,访问方式是非递归中序遍历,也就是说(敲黑板,重点来了):

🍊 入栈时  ->  三个结点一个都未访问  ->  提供前序遍历的机会

🍊 出栈时  -> (不同根节点的)左子树遍历完成  ->  提供中序遍历的机会

第一点大家应该都能看懂,但不出意外的话,第二点标红的地方应该出现了意外,利用下面的二叉树进行解释。

示例二叉树

在指向结点4的时候,首先将结点4压栈,并访问结点4的左子树,左子树为空,按照上面的代码,此时需要弹栈,栈顶为结点4,那么此时弹栈是因为结点4的左子树遍历完成。

另一种情况,遍历到5的时候,同样先将5压栈,再访问左子树,左子树为空,同样将5弹栈,这类似于上一种情况,即5的左子树遍历完成需要弹栈。

然后再遍历5的右子树,右子树为空,根据上方代码,同样需要弹栈,但是,注意,此时直接将1弹栈(2已经遍历过哦),仔细一看这里弹栈是因为1的左子树遍历完成了!其他情况自行尝试,现在应该明白上面的红体字了吧。

言归正传,上面的非递归前序中序遍历代码只能提供入栈和出栈时的两种遍历方式,对于非递归后序遍历无效,为什么这样说呢?又怎么修改代码呢?好的,我来解释一下:

🍐 我们可以看上面的圈3,也就是第三点,假设我们可以将上面的代码改为后序遍历的话,在遍历完5的右子树后直接访问结点1的右子树,并没有回到结点2,也就没办法进行后序遍历。

🍐 怎么修改才可以进行后序遍历呢?首先要在遍历完右子树的时候能够回到根节点,并且要区分从左子树返回和从右子树返回,因为从左子树返回需要遍历右子树,从右子树返回不需要,详见下一章。


三、非递归后序遍历算法

代码在下方,详细分析:

🍅 首先,声明了一个堆栈stk用于存储遍历过程中的节点,并声明了三个指针变量:

  • cur用于存储当前正在处理的节点。
  • last_vist用于存储上一个被访问过的节点,用于区分当前节点是从左子树返回还是从右子树返回。
  • top用于存储堆栈顶部的节点。

🍅  然后,定义了一个循环,每次迭代中都会执行以下操作:

  • 如果cur 不为空,则将cur 压入堆栈中,并将cur 设置为其左子节点。
  • 否则,取出堆栈顶部的节点,并执行以下操作:
    • 如果堆栈顶部的节点有右子节点且没有被访问过,则将cur 设置为右子节点并继续遍历。
    • 否则,输出堆栈顶部的节点的数据,将最后访问的节点设置为堆栈顶部的节点,并将堆栈顶部的节点弹出堆栈。

🍅  循环条件为cur 不为空或者堆栈不为空。循环结束后,后序遍历完成。

与非递归前序、中序遍历的区别:

🍉 将之前代码入栈、出栈两个操作所提供的前序、中序遍历,扩展为入栈、取栈、出栈三个操作,其中入栈代表首次遍历到根节点,取栈代表从左子树返回准备遍历右子树,出栈代表右子树为空或右子树遍历完成。(添加取栈操作,遍历完根节点右子树后会回到根结点)

🍉 使用last_visit区分左子树返回,还是右子树返回。

void post_order_traverse_none(BiTree &bitree)
{// 将每一个非#元素看作根节点遍历一遍stack<BiTree> stk;BiTree cur = bitree;BiTree last_vist = NULL;// 用来区分返回根节点时,是从左子树返回的,还是右子树返回的BiTree top = NULL;while(cur || !stk.empty()){if(cur)// 如果cur不为空,将其压入栈中,并访问左子树{stk.push(cur);cur = cur->lchild;}else// 如果当前结点cur为空,取出栈顶元素{ top = stk.top();// 取出栈顶元素if(top->rchild && last_vist != top->rchild)// 如果栈顶结点的右子节点不为空,且没有被访问过{cur = top->rchild;// 如果cur指向的左子树为空,从栈顶取出根节点,将cur指向其右子树}else// 右子节点为空,且从右子树返回(一定是右子树返回,无论右子树是否为空){// 否则,访问栈顶元素cout<< top->data<< "输出"<<endl;// 将栈顶元素设为最后访问的元素last_vist = top;// 将栈顶元素出栈stk.pop();}}}
}

四、加入圈子

层次遍历:从上到下,从左往右,一层层的遍历。

欢迎球球群交流,层次遍历


http://www.ppmy.cn/news/5510.html

相关文章

C++之多态(中篇)(最全总结)

这里接上面C之多态&#xff08;上篇&#xff09; 本篇目录4.多态的原理4.2 多态的原理4.3 C 11 override和final4.4 重载、重写&#xff08;覆盖&#xff09;、隐藏&#xff08;重定义&#xff09;的对比 &#xff08;函数之间的关系&#xff09;5.抽象类5.1概念5.2接口继承和实…

springDataRedis快速入门

SpringData是Spring中数据操作的模块&#xff0c;包含对各种数据库的集成&#xff0c;其中对Redis的集成模块就叫做SpringDataRedis&#xff0c; 官网地址:https://spring.io/projects/spring-data-redis 提供了对不同Redis客户端的整合( Lettuce和Jedis)提供了RedisTemplate…

简单Thinkphp5.1如何使用Topsdk\Topapi

一淘模板&#xff08;56admin.cn&#xff09;给大家介绍tp5.1相关知识&#xff0c;其中主要记录tp5.1是怎么使用Topsdk\Topapi&#xff08;对接淘宝客开放平台&#xff09;&#xff0c;希望对需要的朋友有所帮助&#xff01; 1、公司有一项目需要对接淘宝开放平台 先去申请帐号…

第十九章 使用系统监视器 - 配置系统监视器命名空间

文章目录第十九章 使用系统监视器 - 配置系统监视器命名空间配置系统监视器命名空间查看系统监视器状态管理应用程序监视器管理健康监视器查看系统数据第十九章 使用系统监视器 - 配置系统监视器命名空间 配置系统监视器命名空间 当一个实例启动时&#xff0c;系统监视器会在…

1362:家庭问题(family)

1362&#xff1a;家庭问题(family) 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 6732 通过数: 3529 【题目描述】 有n个人&#xff0c;编号为1,2,……n&#xff0c;另外还知道存在K个关系。一个关系的表达为二元组&#xff08;α&#xff0c;β&#xff09;形式…

vue3项目打包部署到Tomcat(亲测有效)

首先&#xff0c;要确保电脑上已经安装了jdk&#xff0c;还有Tomcat&#xff0c;而且都安装正确。 jdk下载与安装教程&#xff08;win10&#xff09; Tomcat 9.0 安装及配置教程(win10系统) Vue项目在VScode里面可以通过npm run serve可以正常运行。 下面是打包部署到tomca…

22年D2部分干货

缘起 小编入行以来&#xff0c;一直兢兢业业&#xff0c;跟随大佬们脚步&#xff0c;学习如何努力成为优秀的开发。机缘巧合&#xff0c;在2017年小编接触到了前端峰会——D2&#xff0c;里面好多大佬聊未来的趋势。记得疫情之前&#xff0c;D2采用的是线下邀请&#xff0c;可惜…

浅谈安科瑞电能预付费系统在大电力客户中的设计及应用分析

摘 要 随着我国供电企业的不断发展&#xff0c;而用电模式也在不断改革&#xff0c;预付费技术在气、电等部门得到普遍的使用&#xff0c;本文主要针对预付费系统在大电力客户中的使用情况进行分析&#xff0c;提高用电用户的缴费率&#xff0c;有效的避免了客户恶意偷窃电行…