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

目录

前言

算法思想

非递归实现前序遍历

过程分析

代码

非递归实现中序遍历

过程分析

代码

非递归实现后序遍历

过程分析

代码


前言

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.rhkb.cn/news/333922.html

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

相关文章

使用Python类的构造函数和析构函数

1、问题背景 当使用Python类时&#xff0c;可以使用构造函数和析构函数来初始化和清理类实例。构造函数在创建类实例时自动调用&#xff0c;而析构函数在删除类实例时自动调用。 在上面的代码示例中&#xff0c;Person类具有一个构造函数__init__和一个析构函数__del__。构造…

深度学习-序列模型

深度学习-序列模型 1. 定义2. 应用领域3. 典型模型4. 技术细节5. 总结 序列模型是一种处理序列数据的机器学习模型&#xff0c;其输入和/或输出通常为序列形式的数据。以下是关于序列模型的详细解释&#xff1a; 1. 定义 序列模型是输入输出均为序列数据的模型&#xff0c;它…

宝塔:如何在宝塔面板做301重定向

如何在宝塔面板做301重定向?301重定向对于网站来说非常重要。如果你的网站以www开头&#xff0c;我们应该把没有www的域名重定向到有www的域名&#xff0c;反之亦然。 1、我们进入宝塔管理后台 2、登录面板并单击添加站点。既然要把xxx.com 301发到www.xxx.com&#xff0c;我…

R18 NTN中的RACH-less HO

在看R18 38.300时,发现NTN场景 增加了如下黄色字体的内容,R18 NTN支持了RACH-less HO,索性就简单看了看。 NTN RACH less HO相关的描述主要在38.331,38.213和38.321中。38.300中的描述显示:网络侧会通过RRCReconfiguration消息将RACH-less HO相关的配置下发给UE, 其中会包…

迈向F5G-A,开启全光万兆新时代——南通移动完成全市首个50G-PON技术验证

近日&#xff0c;南通移动在崇川区完成全市首个50G-PON万兆技术现网验证&#xff0c;标志着南通成为首批具备F5G-A(The 5th GenerationFixed Network-advanced)的万兆光网城市&#xff0c;使其成为网速最快、覆盖最全、时延最低的城市之一。 作为全光万兆的关键技术&#xff0c…

Linux: network: TCP: zero window size/window full 示例

最近遇到一个问题,当前机器的CPU使用率非常高,然后导致其中一个程序处理socket的数据过慢,然后出现下面的zero的示例。 下面是在接收buff用光的时候,发出的 TCP zeroWindows的消息 这种问题就是内存,CPU,网速之间的性能取舍。具体解决的话,需要看具体的需要是什么样的?…

2024 年 5 个 GO REST API 框架

什么是API&#xff1f; API是一个软件解决方案&#xff0c;作为中介&#xff0c;使两个应用程序能够相互交互。以下一些特征让API变得更加有用和有价值&#xff1a; 遵守REST和HTTP等易于访问、广泛理解和开发人员友好的标准。API不仅仅是几行代码&#xff1b;这些是为移动开…

优思学院:质量工程师必备技能清单,你具备了吗?

想要了解质量工程师需要具备哪些技能和知识&#xff0c;最直接且实际的方法就是分析招聘广告中的关键词&#xff0c;这比道听途说更加有效。为此&#xff0c;优思学院搜集了大量关于质量工程师职位的招聘信息&#xff0c;并为大家进行详细分析。我们通常选择中高级职位进行分析…

机器人运动轨迹学习——GMM/GMR算法

机器人运动轨迹学习——GMM/GMR算法 前置知识 GMM的英文全称为&#xff1a;Gaussian mixture model&#xff0c;即高斯混合模型&#xff0c;也就是说&#xff0c;它是由多个高斯模型进行混合的结果&#xff1a;当然&#xff0c;这里的混合是带有权重概念的。 一维高斯分布 GMM中…

简化跨网文件传输摆渡过程,降低IT人员工作量

在当今数字化时代&#xff0c;IT企业面临着日益增长的数据交换需求。随着网络安全威胁的不断演变&#xff0c;网关隔离成为了保护企业内部网络不受外部威胁的重要手段。然而&#xff0c;隔离的同时&#xff0c;企业也需要在不同网络间安全、高效地传输文件&#xff0c;这就催生…

mybatisplus填充公共字段MetaObjectHandler后不生效解决方式

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component;import java.util.Date;/*** 拦截处理公共字…

芋道源码 / yudao-cloud:前端技术架构探索与实践

摘要&#xff1a; 随着企业信息化建设的深入&#xff0c;后台管理系统在企业运营中扮演着至关重要的角色。本文将以芋道源码的yudao-cloud项目为例&#xff0c;深入探讨其前端技术架构的设计思路、关键技术与实现细节&#xff0c;并分享在开发过程中遇到的挑战与解决方案。 一、…

经典神经网络(9)VAE模型原理及其在MNIST数据集上的应用

经典神经网络(9)VAE模型原理及其在MNIST数据集上的应用 图片生成领域来说&#xff0c;有四大主流生成模型&#xff1a;生成对抗模型&#xff08;GAN&#xff09;、变分自动编码器&#xff08;VAE&#xff09;、流模型&#xff08;Flow based Model&#xff09;、扩散模型&#…

【智能家居入门1】环境信息监测(STM32、ONENET云平台、微信小程序、HTTP协议)

作为入门本篇只实现微信小程序接收下位机上传的数据&#xff0c;之后会持续发布如下项目&#xff1a;①可以实现微信小程序控制下位机动作&#xff0c;真正意义上的智能家居&#xff1b;②将网络通讯协议换成MQTT协议再实现上述功能&#xff0c;此时的服务器也不再是ONENET&…

数据结构—队列(C语言实现)

文章目录 前言一、队列的概念二、队列的实现Queue.hQueue.c 三、设计循环队列问题数组实现链表实现 总结 前言 嗨喽喽&#xff01;&#xff01;小伙伴们&#xff0c;大家好哇&#xff0c;欢迎来到我的博客&#xff01; 今天将要分享的是另一种数据结构—队列&#xff0c;以及…

五分钟搭建一个Suno AI音乐站点

五分钟搭建一个Suno AI音乐站点 在这个数字化时代&#xff0c;人工智能技术正以惊人的速度改变着我们的生活方式和创造方式。音乐作为一种最直接、最感性的艺术形式&#xff0c;自然也成为了人工智能技术的应用场景之一。今天&#xff0c;我们将以Vue和Node.js为基础&#xff…

MySQL触发器实战:自动执行的秘密

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 MySQL触发器实战&#xff1a;自动执行的秘密 前言触发器的定义和作用触发器的定义和作用触发器的…

leetCode.82. 删除排序链表中的重复元素 II

leetCode.82. 删除排序链表中的重复元素 II 题目思路&#xff1a; 代码 class Solution { public:ListNode* deleteDuplicates(ListNode* head) {auto dummy new ListNode(-1);dummy->next head;auto p dummy;while(p->next){auto q p->next->next;while(q …

插件“猫抓”使用方法 - 浏览器下载m3u8视频 - 合并 - 视频检测下载 - 网课下载神器

前言 浏览器下载m3u8视频 - 合并 - 网课下载神器 chrome插件-猫抓 https://chrome.zzzmh.cn/info/jfedfbgedapdagkghmgibemcoggfppbb 步骤&#xff1a; P.s. 推荐大佬的学习视频&#xff01; 《WEB前端大师课》超级棒&#xff01; https://ke.qq.com/course/5892689#term_id…

使用Python操作Jenkins

大家好&#xff0c;Python作为一种简洁、灵活且功能丰富的编程语言&#xff0c;可以与各种API轻松集成&#xff0c;Jenkins的API也不例外。借助于Python中的python-jenkins模块&#xff0c;我们可以轻松地编写脚本来连接到Jenkins服务器&#xff0c;并执行各种操作&#xff0c;…