【二叉树进阶】二叉树的前中后序遍历(非递归迭代实现)

文章目录

  • 1. 二叉树的前序遍历
    • 1.1 思路分析
    • 1.2 AC代码
  • 2. 二叉树的中序遍历
    • 2.1 思路分析
    • 2.2 AC代码
  • 3. 二叉树的后序遍历
    • 3.1 思路1
    • 3.2 思路1AC
    • 3.3 思路2
    • 3.4 思路2AC

1. 二叉树的前序遍历

题目链接: link

在这里插入图片描述

不用递归,用迭代算法如何实现对二叉树的前序遍历?
在这里插入图片描述
最终放到一个vector里面返回。

1.1 思路分析

前序遍历的非递归呢我们可以这样来搞:

题目中给的二叉树比较简单,下面通过这样一棵二叉树给大家讲解:
在这里插入图片描述
对它进行非递归的前序遍历,它是这样搞的:
前序遍历是根、左子树、右子树
所以首先从根结点开始,顺着访问左子树:8、3、1
然后现在还有谁没访问?
🆗,是1的左子树、3的左子树,和8的左子树。
所以下面倒着访问1、3、8的左子树就行了。
所以非递归的前序遍历是这样处理的:
他把一棵二叉树分为两个部分

  1. 左路结点
  2. 左路结点的右子树

在这里插入图片描述
对于每一棵左子树,也是同样划分为这两个部分进行处理。

那现在问题来了,如何倒着去处理左路结点的右子树?

那此时我们就可以借助一个栈来搞。
在这里插入图片描述
还是以这棵树为例,从根结点8开始,依次访问左路结点8,3,1。
在访问过程中除了将他们放到要返回的vector里面,再把左路结点放到栈里面

在这里插入图片描述
然后:
依次取栈顶元素(1 3 8 ),访问它们的右子树。
那这是不是就是一个前序遍历的顺序啊。
那如何处理它们的右子树啊?
🆗,这是不是一个子问题啊。
首先1出栈,访问1的右子树,那为空,就直接结束。
在这里插入图片描述
然后再取栈顶元素3,访问它的右子树。
所以我们此时就要循环上去,从3的右子树的根结点6开始,进行同样的处理。
首先访问3的右子树的左路结点并入栈

在这里插入图片描述
然后4、6出栈,先后处理4、6的右子树,对于子树同样循环上去进行处理。
后续也是如此,我这里就不继续往下画了。
大家如果还不是特别理解可以继续往下画完。

1.2 AC代码

那我们来写一下代码:

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ret;
        stack<TreeNode*> st;
        TreeNode* cur=root;

        //循环结束条件:
        //1.cur不为空表示还有树没有访问
        //2.栈不为空表示还有结点的右子树没处理
        while(cur||!st.empty())
        {
            //遍历左路结点并入栈
            while(cur)
            {
                ret.push_back(cur->val);
                st.push(cur);
                cur=cur->left;
            }
            
            //取栈顶元素并访问它的右子树
            TreeNode* top=st.top();
            st.pop();

            //怎么处理它的右子树?
            //子问题,把cur置成右子树的根,上去重新进行循环即可
            cur=top->right;
        }
        return ret;
    }
};

当然不止我们这里讲的这一种方法,不过我们这个后面比较方便往中序和后序的方向上修改。

2. 二叉树的中序遍历

题目链接: link

接下来我们就来看一下二叉树中序遍历的非递归如何实现
在这里插入图片描述

2.1 思路分析

其实大体的思路还是跟上一道题的差不多,最后写出来跟上一题的代码也基本一样,其中一句代码换一下位置就行了

那我们这里还是,每一棵树都把它分成左路结点和右子树
在这里插入图片描述
回忆一下上一题我们的前序是怎么走的:、
在这里插入图片描述
我们是在左路结点入栈的时候就把它放到要返回的vector里面,因为这就符合前序遍历的顺序。
那现在是中序遍历,中序是先访问左子树,然后再访问根
所以我们先把左路结点入栈,但是不放进vector里面。
在这里插入图片描述
一直走到1的左子树为空然后停止入栈,那这时就可以认为1的左子树是空已经遍历过了。
然后出栈里面的元素(从栈里面取出一个左路结点的时候,就意味着它的左子树已经访问过了),第一个出的是1,那此时遇到1我们要把它放到vector里面吗?
🆗,这时就要放了,因为1的左子树访问过后,就要访问根了(左子树、根、右子树)
在这里插入图片描述
那1的根访问完,然后要访问柚子是,这里1的右是空,但是其它测试用例不一定是空啊。
那要访问右子树怎么办?
是不是还是让它循环上去,从当前左路结点的右子树的根开始进行同样的处理。
当然这里1的右是空,所以下面就直接取栈顶下一个元素了,那就是3
在这里插入图片描述
然后处理3的右子树。
循环上去,从根结点6开始进行同样的处理
左路结点6,4入栈
在这里插入图片描述
然后4出栈,处理4的左,左为空。
接着6出栈,处理6的左
在这里插入图片描述
那对于6的左,就是循环上去,对7这棵子树进同样的处理
7入栈,左为空,不再继续入了。
接着7出栈,放进vector。
在这里插入图片描述
接着处理7的左。
后续也是一样,8出栈,然后处理8的左
大家看现在的访问顺序是不是中序的
在这里插入图片描述
后面我就不画了。

2.2 AC代码

那代码很简单,跟上一题相比,是不是就是入vector的时机变了啊

前序是入栈的时候就放到vector里面,中序是出栈的时候在放到vector里面
在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ret;
        stack<TreeNode*> st;
        TreeNode* cur=root;

        while(cur||!st.empty())
        {
            //遍历左路结点并入栈
            while(cur)
            {
                st.push(cur);
                cur=cur->left;
            }
            
            //取栈顶元素的时候再入vector
            TreeNode* top=st.top();
            st.pop();
            ret.push_back(top->val);

            //处理右子树
            //子问题,把cur置成右子树的根,上去重新进行循环即可
            cur=top->right;
        }
        return ret;
    }
};

3. 二叉树的后序遍历

题目链接: link
在这里插入图片描述
那后序遍历的非递归又如何实现呢?
这里提供两种思路

3.1 思路1

思路1呢是这样的:

大家想前序是根、左子树、右子树。
后序是左子树、右子树、根。
那如果我们实现一个根、右子树、左子树的遍历,然后把得到的vector逆置一下是不是就是后序遍历的结果啊。
那怎么能够得到一个根、右子树、左子树的遍历呢?
🆗,我们把前序遍历的代码修改一下,访问完根之后先访问右子树、在访问左子树不就行了嘛。
在这里插入图片描述

3.2 思路1AC

很简单,修改两句代码的事:

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ret;
        stack<TreeNode*> st;
        TreeNode* cur=root;

        //循环结束条件:
        //1.cur不为空表示还有树没有访问
        //2.栈不为空表示还有结点的左子树没处理
        while(cur||!st.empty())
        {
            //遍历右路结点并入栈同时入vector
            while(cur)
            {
                ret.push_back(cur->val);
                st.push(cur);
                cur=cur->right;
            }
            
            //取栈顶元素并访问它的左子树
            TreeNode* top=st.top();
            st.pop();

            //怎么处理它的左子树?
            //子问题,把cur置成左子树的根,上去重新进行循环即可
            cur=top->left;
        }
        reverse(ret.begin(),ret.end());
        return ret;
    }
};

3.3 思路2

那如果我们就想像上面的前序中序那样按照正确的顺序去实现遍历呢?而不是用刚才这种取巧的方法:

后序遍历是左子树、右子树、根;
而中序遍历是左子树、根、右子树
所以,后序遍历前面的操作和中序是一样的:
还是先让左路结点入栈在这里插入图片描述
然后对于栈顶的元素我们可以直接让它入vector然后pop掉嘛。
中序我们就是这样做的,因为从栈里面取出一个左路结点的时候,就意味着它的左子树已经访问过了,然后中序的话该访问根了,而把栈顶元素放到vector里面然后pop掉就相当于访问根结点。
但是我们后序就不能直接这样了,因为后序要在右子树访问完之后再去访问根

那怎么办?

其实很简单,加一个判断就行了。
能不能直接pop,然后放到vector里面,其实要看情况:
在这里插入图片描述
大家看对于1这种情况我们可不可以直接访问,是不是可以啊,因为1的右子树为空。
那如果是6这种情况呢?
就不可以了,因为它的右子树不为空,所以要先访问右子树7。
那7访问完把7pop掉之后回到6这里,这次可以访问6了吗?
那这时就可以了,因为6的右子树访问过了。
所以两种情况我们可以直接访问根:

  1. 右子树为空
    如果右子树不为空,就不能值访问根,要先访问右子树
  2. 右子树已经访问过了

那右子树为空,这很好判断,但是如何判断一个结点的右子树是否已经访问过了呢?
🆗,我们可以定义一个prev指针,每次处理完一个结点pop掉之后,把这个结点赋值给prev。
然后我们就可以通过prev判断某个结点前面被访问的结点是不是它的右子树
prev==top.right

那思路呢就是这样的,我们来写一下代码:

3.4 思路2AC

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ret;
        stack<TreeNode*> st;
        TreeNode* cur=root;

        TreeNode* 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)
            {
                st.pop();
                ret.push_back(top->val);

                //记录prev
                prev=top;
            }
            //否则,就需要先处理右子树
            else
            {
                cur=top->right;
            }
        }
        return ret;
    }
};

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

QT实现中英文键盘

使用Qt中实现中英文键盘&#xff0c;支持各种linux嵌入式设备。 实现思路&#xff1a;需要一个中文字体库&#xff0c;将字体库加载到一个Hash容器&#xff0c;字母和拼音作为key值&#xff0c;对应的中文作为value值。 核心代码&#xff1a; #include "UKeyBoard.h"…

【雕爷学编程】MicroPython动手做(28)——物联网之Yeelight 2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

RISC-V基础之函数调用(三)保留寄存器(包含实例)

RISC-V将寄存器分为保留和非保留两类。保留寄存器是指在函数调用前后必须保持相同值的寄存器&#xff0c;因为调用者期望在调用后能够继续使用这些寄存器的值。保留寄存器包括s0到s11&#xff08;因此称为saved&#xff09;&#xff0c;sp和ra。非保留寄存器&#xff0c;也称为…

数据可视化(六)多个子图及seaborn使用

1.多个子图绘制 #绘制多个子图 #subplot&#xff08;*args&#xff0c;**kwargs&#xff09; 每个subplot函数只能绘制一个子图 #subplots&#xff08;nrows&#xff0c;ncols&#xff09; #fig_add_subplot(行&#xff0c;列&#xff0c;区域) #绘制子图第一种方式 plt.subp…

网络安全进阶学习第十课——MySQL手工注入

文章目录 一、MYSQL数据库常用函数二、MYSQL默认的4个系统数据库以及重点库和表三、判断数据库类型四、联合查询注入1、具体步骤&#xff08;靶场演示&#xff09;&#xff1a;1&#xff09;首先判断注入点2&#xff09;判断是数字型还是字符型3&#xff09;要判断注入点的列数…

网工内推 | 云计算工程师专场,CCNP/HCIP认证优先

01 弧聚科技 招聘岗位&#xff1a;网络工程师&#xff08;云计算方向&#xff09; 职责描述&#xff1a; 1、作为H3C初级云计算交付工程资源培养对象&#xff0c;需配合完成相关华三产品及服务规范培训。 2、培训赋能后&#xff0c;安排到H3C云项目交付中进行项目交付及驻场支…

【Nginx13】Nginx学习:HTTP核心模块(十)Types、AIO及其它配置

Nginx学习&#xff1a;HTTP核心模块&#xff08;十&#xff09;Types、AIO及其它配置 今天学习的内容也比较简单&#xff0c;主要的是 Types 相关的配置&#xff0c;另外还会了解一下 AIO 以及部分没有特别大的分类归属的配置指令的使用。后面的内容都是 HTTP 核心模块中比较小…

核心交换机新增了一个网段,现在下面PC可以获取地址访问内网 ,访问外网说DNS有问题不通

环境: SANGFOR AF 8.0.75 SANGFOR AC 13.0.47 H3C S6520-26Q-SI 问题描述: 1.在核心交换机上新规划了一个网段192.168.200.0/24,现在下面PC可以正常获取IP地址和DNS,正常访问内网服务和其它地址段IP ,访问外网说DNS有问题不通打不开网页 2.DNS解析失败,ping dns服务…

Kubernetes高可用集群二进制部署(二)ETCD集群部署

Kubernetes概述 使用kubeadm快速部署一个k8s集群 Kubernetes高可用集群二进制部署&#xff08;一&#xff09;主机准备和负载均衡器安装 Kubernetes高可用集群二进制部署&#xff08;二&#xff09;ETCD集群部署 Kubernetes高可用集群二进制部署&#xff08;三&#xff09;部署…

第四次作业 运维高级 安装tomcat8和部署jpress应用

1. 简述静态网页和动态网页的区别。 静态网页 静态网页是指存放在服务器文件系统中实实在在的HTML文件。当用户在浏览器中输入页面的URL&#xff0c;然后回车&#xff0c;浏览器就会将对应的html文件下载、渲染并呈现在窗口中。早期的网站通常都是由静态页面制作的。 静态网页…

银河麒麟V10 wireshark安装说明(断网离线)

下载离线安装包 链接&#xff1a;https://pan.baidu.com/s/11QFRmCGlIJrJaiKcHh9Hag?pwdu9wv 提取码&#xff1a;u9wv 安装步骤 tar zxvf wireshark.tar.gz cd wireshark sudo dpkt -i *.deb wireshark

构建vue项目配置和环境配置

目录 1、环境变量process.env配置2、vue package.json多环境配置vue-cli-service serve其他用法vue-cli-service build其他用法vue-cli-service inspect其他用法3、vue导出webpack配置4、配置打包压缩图片文件5、打包去掉多余css(由于依赖问题暂时未实现)6、打包去除console.…

【每日一题】—— C. Challenging Cliffs(Codeforces Round 726 (Div. 2))

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…

Spring Boot 系列4 -- 统一功能处理

目录 前言 1. Spring AOP 用户统⼀登录验证的问题 1.1 自定义拦截器 1.2 配置拦截器并配置拦截的规则 1.3 拦截器的原理源码分析 2. 统一异常处理 2.1 实现统一异常处理 2.2 测试统一异常处理 3. 统一的数据格式返回 3.1 统⼀数据返回格式的实现 3.2 测试统一的数据返…

智慧水务和物联网智能水表在农村供水工程中的应用

摘 要&#xff1a;随着社会的进步和各项事业的飞速发展&#xff0c;人民生活水平的逐步提升&#xff0c;国家对农村饮水安全有了更高的要求&#xff0c;为了进一步提升农村供水服务的质量&#xff0c;利用现代化、信息化科学技术提升农村供水服务质量&#xff0c;提高用水管理效…

GB28181智能安全帽方案探究及技术实现

什么是智能安全帽&#xff1f;​ 智能安全帽是一种集成先进科技的安全帽&#xff0c;可基于GB28181规范&#xff0c;适用于铁路巡检、电力、石油化工等高风险行业的作业人员&#xff0c;以及消防、救援等紧急情况下的安全防护。 智能安全帽通常具有以下功能&#xff1a; 实时…

RocketMQ发送消息超时异常

说明&#xff1a;在使用RocketMQ发送消息时&#xff0c;出现下面这个异常&#xff08;org.springframework.messging.MessgingException&#xff1a;sendDefaultImpl call timeout……&#xff09;&#xff1b; 解决&#xff1a;修改RocketMQ中broke.conf配置&#xff0c;添加下…

git使用(由浅到深)

目录流程图 1. 分布式版本控制与集中式版本控制 1.1 集中式版本控制 集中式版本控制系统有:CVS和SVN它们的主要特点是单一的集中管理的服务器&#xff0c;保存所有文件的修订版本&#xff1b;协同开发人员通过客户端连接到这台服务器&#xff0c;取出最新的文件或者提交更新…

天气API强势对接

&#x1f935;‍♂️ 个人主页&#xff1a;香菜的个人主页&#xff0c;加 ischongxin &#xff0c;备注csdn ✍&#x1f3fb;作者简介&#xff1a;csdn 认证博客专家&#xff0c;游戏开发领域优质创作者,华为云享专家&#xff0c;2021年度华为云年度十佳博主 &#x1f40b; 希望…

“苏豪 x 莱佛士”再度携手,惊艳亮相上海发型师节!

2023年6月28日&#xff0c;以“为爱启航”为主题的第16届AHF亚洲发型师节在上海跨国采购中心盛大开幕。继上次在施华蔻专业2023春夏新季风发布会上&#xff0c;苏豪x莱佛士合作的大秀&#xff0c;赢得了现场观众阵阵掌声。这次“Kraemer苏豪x莱佛士”再度携手&#xff0c;惊艳亮…