【二叉树】非递归实现前中后序遍历

目录

前言

算法思想

非递归实现前序遍历

过程分析

代码

非递归实现中序遍历

过程分析

代码

非递归实现后序遍历

过程分析

代码


前言

1)前序: 左子树 右子树

2)中序:左子树 右子树

3)后序:左子树  右子树

可以看出,这三种遍历方式的本质区别在于什么时候访问根节点,下面将介绍一种很厉害的思想,对于前中后序的非递归实现,它可以是通解。

算法思想

将二叉树分割成两部分:

1)左路节点

2)左路节点的右子树

它的每一棵子树也可以继续分为左路节点和左路节点的右子树。

实现这种算法,我们需要借助一种数据结构——栈,同时,为了存储数据我们还需要借助另一种数据结构——vector。

 下面,把这种算法转换成代码。

非递归实现前序遍历

过程分析

以这棵树为例:

1)所有左路节点进栈。

2)根据前序遍历的规则,所遍历到的结点都可以直接反问,即可以直接保存进vector中, 因为每个节点都可以是根。

3)左路结点遍历完后,出栈,在出栈时,如果它的右子树不为空,则将它的右子树的左路节点入栈,重复此操作,就可以遍历完左路节点对应        的右子树。

以上图为例:

所有左路节点进栈(面向屏幕右手边为栈顶):

stack:8  3  1 

vector:8  3  1

考虑1出栈,考虑到1的右子树为空,所以可以直接出栈。下面更新stack和vector。

stack:8  3

vector:8  3  1

考虑3出栈,考虑到3的右子树不为空,所以3出栈后,要把3的右子树的左路节点6和4依次入栈(结点3会有记录,不会找不到它的右树)。下面更新stack和vector。

stack:8  6  4

vector:8  3  1  6  4

考虑4出栈,考虑到4的右树为空,可以直接出栈。下面更新stack和vector。

stack:8  6 

vector:8  3  1  6  4

考虑6出栈,考虑到6的右子树不为空,所以出栈后,它的右子树的左路节点7要入栈。下面更新stack和vector。

stack:8  7

vector:8  3  1  6  4  7

考虑7出栈,考虑到7的右子树为空,所以可以直接出栈。下面更新stack和vector。

stack:8  

vector:8  3  1  6  4  7

考虑8出栈,考虑到8的右子树不为空,所以出栈后,它的右子树的左路节点10要入栈。下面更新stack和vector。

stack:10  

vector:8  3  1  6  4  7  10

考虑10出栈,考虑到10的右子树为空,所以可以直接出栈。下面更新stack和vector。

stack:空 

vector:8  3  1  6  4  7  10

此时,栈已空,所有结点均已访问完毕,前序遍历结束。

代码

class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {stack<TreeNode*> st;vector<int> ret;TreeNode* cur = root;while(cur || !st.empty()){//所有左路节点入栈while(cur){ret.push_back(cur->val);//遍历到的结点可直接访问st.push(cur);cur = cur->left;}TreeNode* top = st.top();//访问当前节点的右子树,如果不为空,会进入内层的while循环,把它的左路节点入栈//如果为空,则不会进入内存while循环cur = top->right;st.pop();}return ret;}
};

非递归实现中序遍历

过程分析

中序遍历和前序遍历尤为相似,它们的本质区别在于什么时候访问根结点。

1)所有左路节点进栈

2)左路节点进栈的同时不能去访问这些节点,因为根据中序遍历的规则,应该先访问左子树(这就是与前序遍历的区别)

3)所有左路节点都进栈以后,意味着最后一个左路节点的左子树(为空)已经访问完毕,可以访问根了,这时,弹出栈顶元素,并把它的值存       储进vector中,如果它有右子树,还应该把它的右子树的左路节点入栈。

代码

class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {stack<TreeNode*> st;vector<int> v;TreeNode* cur = root;while(cur || !st.empty()){//左路节点入栈while(cur){st.push(cur);cur = cur->left;}//退出循环后,意味着左路节点已访问,可以访问根了TreeNode* top = st.top();v.push_back(top->val);//访问根st.pop();cur = top->right;//左路节点的右子树如果非空,则让它的左路节点入栈}return v;}
};

非递归实现后序遍历

过程分析

后序遍历和前面的两种遍历方式其实差不多,都是把整棵树分为左路节点和左路节点的右子树。关键问题在于什么时候访问根。

1)左路节点进栈

2)左路节点进栈是不能直接去访问,根据后序遍历的规则,应该是先访问左子树,右子树再到根。

3)左路节点进栈完毕,意味着可以访问右子树了,如果左路节点的右子树不为空,就让它的左路节点进栈,如果为空,就可以访问根结点了

4)访问完左节点后,如果当前栈顶结点的右子树为空或者上一个访问的结点是它的右节点,满足这两个条件之一就可以访问当前节点了(根)

以上图为例 

所有左路节点进栈(面向屏幕右手边为栈顶):

stack:8  3  1 

vector:空

考虑1出栈,考虑到1的右子树为空,所以可以访问根即节点1,且1出栈。下面更新stack和vector。

stack:8  3

vector:1

考虑3出栈,考虑到3的右子树不为空,且上一个访问的节点不是它的右节点,所以3还不能访问,也还不可以出栈,要把3的右子树的左路节点6和4依次入栈 。下面更新stack和vector。

stack:8  3  6  4

vector:1

考虑4出栈,考虑到4的右树为空,可以直接出栈并访问该节点。下面更新stack和vector。

stack:8  3  6 

vector:1  4

考虑6出栈,考虑到6的右子树不为空且上一个访问的结点不是它的右节点,所以不能出栈,将它的右子树的左路节点7要入栈。下面更新stack和vector。

stack:8  3  6  7

vector:1  4

这里对节点6进行了第一次判断。

考虑7出栈,考虑到7的右子树为空,所以可以直接出栈并访问。下面更新stack和vector。

stack:8  3  6 

vector:1  4  7

考虑6出栈,考虑到6的右结点(7)为上一个访问的结点,所以可以访问6并将6弹出栈。下面更新stack和vector。

stack:8  3

vector:1  4  7  6

这里对节点6进行了第二次判断。可以看出,记录上一个访问的结点是有意义的。

考虑3出栈,考虑到3的右节点(6)为上一个访问的结点,所以可以访问3并弹出栈。下面更新stack和vector。

stack:8

vector:1  4  7  6  3

考虑8出栈,8的右子树不为空且它的右节点(10)不是上一个访问的结点,所以8还不可以出栈,应该将10入栈。下面更新stack和vector。

stack:8  10

vector:1  4  7  6  3

考虑10出栈,10的右树为空,可以访问,并弹出栈。下面更新stack和vector。

stack:8  

vector:1  4  7  6  3  10

考虑8出栈,考虑到上一个访问的结点10为8的右节点,所以8可以访问并弹出栈。下面更新stack和vector。

stack:空 

vector:1  4  7  6  3  10  8

此时,栈已空,所有结点均已访问完毕,后序遍历结束。

代码

class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {stack<TreeNode*> st;vector<int> v;TreeNode* cur = root, *prev = nullptr;while(cur || !st.empty()){//左路节点入栈while(cur){st.push(cur);cur = cur->left;}TreeNode* top = st.top();if(top->right == nullptr || prev == top->right){//访问当前结点v.push_back(top->val);//更新prevprev = top;st.pop();}else{//右树的左路节点入栈cur = top->right;}}return v;}
};

完~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/17443.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

邮箱调用接口的服务有哪些?怎么配置接口?

邮箱调用接口安全性如何保障&#xff1f;使用邮箱服务器的方法&#xff1f; 邮箱调用接口为各种应用和系统提供了便捷的电子邮件发送与接收功能。选择合适的邮箱调用接口服务可以大大提升工作效率和用户体验。本AokSend将探讨一些主要的邮箱调用接口服务。 邮箱调用接口&…

MySQL(进阶)--索引

目录 一.存储引擎 1.MySQL体系结构​编辑 2.存储引擎简介 3.存储引擎特点 (1.InnoDB (2.MyISAM (3.Memory 4.存储引擎选择 二.索引 1.索引概述 2.索引结构 3.索引分类 4.索引语法 (1.创建索引 (2.查看索引 (3.删除索引 5.SQL性能分析 (1.SQL执行频率 (2.慢查…

【Sql Server】随机查询一条表记录,并重重温回顾下自定义函数的封装和使用

大家好&#xff0c;我是全栈小5&#xff0c;欢迎来到《小5讲堂》。 这是《Sql Server》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 前言随机查询语…

Android 中资源文件夹RES/RAW和ASSETS的使用区别

文章目录 1、res/raw 文件夹1.1、特点1.2、使用方法1.3、示例&#xff1a; 2. assets 文件夹2.1、特点2.2、使用方法2.3、示例&#xff1a; 3、使用场景3.1、res/raw 使用场景3.2、assets 使用场景 4、比较与选择5、文件夹选择的建议6、 示例代码总结6.1、res/raw 示例6.2、ass…

电瓶车进电梯识别报警摄像机

随着电动车的普及&#xff0c;越来越多的人选择电动车作为出行工具。在诸多场景中&#xff0c;电梯作为一种常见的交通工具&#xff0c;也受到了电动车用户的青睐。然而&#xff0c;电动车进入电梯时存在一些安全隐患&#xff0c;为了提高电动车进电梯的安全性&#xff0c;可以…

小程序自动化辅助渗透脚本(2024)

简介 1.还在一个个反编译小程序吗&#xff1f; 2.还在自己一个个注入hook吗&#xff1f; 3.还在一个个查看找接口、查找泄露吗&#xff1f; 现在有自动化辅助渗透脚本了&#xff0c;自动化辅助反编译、自动化注入hook、自动化查看泄露 注&#xff1a;本工具仅用于学习交流&…

Java中的JSON神器,如何轻松玩转复杂数据结构

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、揭秘JSON世界的基石 在Java的世界中&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;它基于文本&#xff0c;易于阅读和编写&#xff0c;同时也易于…

站内信设计

参考文章&#xff1a;https://cloud.tencent.com/developer/article/1684449 b站站内信业务设计&#xff1a; 消息的类型分为&#xff1a; 1、系统消息 2、、点赞、回复等用户行为之间的消息(事件提醒) 3、用户之间的消息 系统消息 用一个用户消息表可以吗&#xff1f; 可…

XS2185一款八通道以太网供电控制器

XS2185是一款八通道以太网供电控制器。 XS2185通过侦测各通道的DET管脚输入电压 来判断是否有合格的负载/PD接入系统&#xff0c;以决定 是否开启MOS供电开关。 当通道已经处于供电状态时&#xff0c;XS2185通过侦 测SENSE管脚的输入电压&#xff0c;以判断供电是否发生 …

免费,Python蓝桥杯等级考试真题--第15级(含答案解析和代码)

Python蓝桥杯等级考试真题–第15级 一、 选择题 答案&#xff1a;B 答案&#xff1a;D 解析&#xff1a;集合的并集运算有两种方式&#xff0c;一种是使用“|”运算符进行操作&#xff0c;另一种是使用union()方法来实现&#xff0c;故答案为D。 答案&#xff1a;A 解析&…

Caused by: java.lang.IllegalArgumentException: Unknown flag 0x1000

Dubbo使用Tomcat安装admin2.5.x管理平台时发生的错误: Caused by: java.lang.IllegalArgumentException: Unknown flag 0x1000 解决方法&#xff1a; 将本地的jdk环境变量切换成jdk8即可。

[ C++ ] 类和对象( 下 )

初始化列表 初始化列表&#xff1a;以一个冒号开始&#xff0c;接着是一个以逗号分隔的数据成员列表&#xff0c;每个"成员变量"后面跟 一个放在括号中的初始值或表达式。 class Date { public: Date(int year, int month, int day): _year(year), _month(month), _d…

视频汇聚/云存储/安防监控EasyCVR接入GB28181设备未回复ack信息的原因排查

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。 用户反馈&#xff0c;设备通过国标GB28181注…

kubeadm引导欧拉系统高可用的K8S1.28.X

文章目录 一. 核心组件架构二. 有状态与无状态应用三. 资源对象3.1 规约与状态3.2 资源的分类-元数据,集群,命名空间3.2.1 元数据3.2.2 集群资源 3.3 命名空间级3.3.1 pod3.3.2 pod-副本集3.3.3 pod-控制器 四. Kubeadm安装k8s集群4.1 初始操作4.2 ~~所有节点安装Docker&#x…

关于高性能滤波器和普通型滤波器的区别说明

高性能滤波器和普通型滤波器在性能和滤波效果上存在显著差异。以三安培为代表分析高性能滤波器和普通型滤波器的区别&#xff1a; 从上图曲线可看出&#xff1a; 1.高性能滤波器和普通型滤波器的滤波范围不同。普通型滤波器有效滤波范围为 150KHz~30MHz&#xff0c;而高性能滤…

【css3】02-css3新特性之选择器篇

目录 1 属性选择器 2 结构伪类选择器 3 其他选择器 :target和::selection ::first-line和::first-letter 4 伪类和伪元素的区别 伪类&#xff08;Pseudo-classes&#xff09; 伪元素&#xff08;Pseudo-elements&#xff09; 伪类和伪元素的区别 1 属性选择器 ☞ 属性选…

螺旋矩阵(算法题)

文章目录 螺旋矩阵解题思路 螺旋矩阵 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]解题思路 模…

人类和小鼠转录组上游分析

基础软件 conda install cutadapt, trimmomatic, samtools, hisat2, subread, deeptools -y人类转录组上游分析 # 样本名称 sample_namesample# 线程 threads4# 双端测序原始fastq1和fastq2路径 fastq1_path/path/${sample_name}_1.fq.gz fastq2_path/path/${sample_name}_2.…

5倍收益秘诀:APP广告如何变现?

在这个数字时代&#xff0c;智能手机几乎成了我们生活中不可或缺的一部分。无论是早晨醒来的第一件事&#xff0c;还是睡前的最后一件事&#xff0c;手机都与我们紧密相连。而在这个连接的世界里&#xff0c;APP广告变现成为了一个热门话题&#xff0c;它不仅仅是将每一次点击转…

Redis与数据库同步指南:订阅Binlog实现数据一致性

本文作者:小米,一个热爱技术分享的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! 大家好,我是29岁的小米,一名积极活泼、热爱分享技术的开发者。今天,我们来聊聊分布式系统中的一个重要话题——分布式一致性,特别是数据库和R…