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

news2024/11/19 20:30:09

文章目录

  • 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.coloradmin.cn/o/830698.html

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

相关文章

linux作业

1.简述静态网页和动态网页的区别 (1).程序是否在服务器端运行&#xff0c;是重要标志。 (2).编程技术不同。静态网页和动态网页主要根据网页制作的语言来区分。 (3).被搜索引擎收录情况不同。 (4).用户访问速度不同。 (5).制作和后期维护工作量不同。 2. 简述 Webl.0 和 …

嵌入式pc技术的特点有哪些?

嵌入式PC技术是将计算机硬件和软件嵌入到各种设备中的一种技术&#xff0c;它具有低功耗、高效率、小型化、易于集成等优点&#xff0c;广泛应用于工业自动化、医疗设备、电力、通信、家用电器、物联网等领域&#xff0c;成为新时代工业生产和社会生活必不可少的技术之一。 嵌入…

Python基本数据类型之散列类型详解

前言&#xff1a; python的基本数据类型可以分为三类&#xff1a;数值类型、序列类型、散列类型&#xff0c;本文主要介绍散列类型。 一、散列类型 散列类型&#xff1a;内部元素无序&#xff0c;不能通过下标取值 1&#xff09;字典&#xff08;dict&#xff09;&#xff…

SAP 特殊采购类型52简介

特殊采购类型52简介-52 直接生产/收集订单可以在物料主数据中进行设置或者在BOM中进行设置, 所谓“直接生产”,是相对于一般的“间接生产”模式而言的。在我们通常采用的计划模式下面,所有在BOM结构里面的半成品,都是在库存中作为一个整体, 可以用在任意的一个成品物料,或…

STM32 NOR_FLASH 学习

NOR FLASH FLASH是常用的&#xff0c;用于存储数据的半导体器件&#xff0c;它具有容量大&#xff0c;可重复擦写、按“扇区/块”擦除、掉电后数据可继续保存的特性。 NOR FLASH的单位是MB&#xff0c;EEPROM的单位是KB。 NM25Q128&#xff0c;是NOR FLASH的一种&#xff0c…

正则匹配img标签里面src

正则&#xff1a; (?<src\s*\s*\")\S(?\"{1})匹配效果&#xff1a;

SAP F4下拉值报错:【内部错误:表格格式】

报错截图如下&#xff1a; 解决办法&#xff1a; 事务码&#xff1a;SU3 在【参数】页签维护如下值&#xff1a; SET/GET参数标识参数值简短描述F4METHODNoActiveXActiveX/NoActiveX 维护好以上信息之后&#xff0c;就可以正常显示下拉值了

OpenHarmony ArkUI 如何调用相机

​ ArkUI调用相机和调用相册其实是一个思路&#xff0c;只用修改一个地方。 我们继续来说相机调用&#xff0c;ArkUI没办法自己获取相机&#xff0c;所以得依靠一下ohos.multimedia.camera 相机开发指导 介绍 本指导主要展示了调用相机的调用过程&#xff0c;以及调用相机的…

【Elasticsearch】学好Elasticsearch系列-Query DSL

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 先看后赞&#xff0c;养成习惯。 点赞收藏&#xff0c;人生辉煌。 文章目录 查询上下文相关度评分&#xff1a;_score源数据&#xff1a;_source数据源过滤器全文检索match&#xff1a;匹配包含某个term的子句…

整个个人博客?想找纯html代码模板?来个手机版带菜单的首页模板“参考参考”

以前做毕业设计的时候老想找一些不掺杂后端代码的前端模板。 可是下载下来&#xff0c;不是php就是python后台的。看又看不懂&#xff0c;想换语言就必须先把里面的后台代码拿掉。 就很像买了个精装的二手房&#xff0c;白白多花了砸墙钱。 就比如&#xff0c;想做个带菜单的…

【go-zero】docker镜像直接部署API与RPC服务 如何实现注册发现?docker network 实现 go-zero 注册发现

一、场景&问题 使用docker直接部署go-zero微服务会发现API无法找到RPC服务 1、API无法发现RPC服务 用docker直接部署 我们会发现API无法注册发现RPC服务 原因是我们缺少了docker的network网桥 2、系统内查看 RPC服务运行正常API服务启动,通过docker logs 查看日志还是未…

数据库相关知识点

体系结构图&#xff1a; 体系介绍&#xff1a; Client Connectors 接入方。支持很多协议(JDBC、ODBC、.NET、PHP、Python、PERL、C 等) Management Serveices & Utilities 系统管理和控制工具&#xff0c;mysqldump、 mysql复制集群、分区管理等 Connection Pool 连接池…

【css】css位置布局position

position 属性规定应用于元素的定位方法的类型。元素其实是通过使用top、bottom、left 和 right 属性来定位的。但是&#xff0c;需要首先设置了 position 属性&#xff0c;否则这些属性将不起作用。根据不同的 position 值&#xff0c;它们的设置特点不同。 其有五个不同的位…

【禅道】禅道数据迁移,源码安装的禅道18.2迁移至docker安装的禅道18.2

docker安装禅道开源版18.2 sudo docker run --name chandao \ -p 9080:80 \ -p 3306:3306 \ --networkzentaonet \ -v /opt/docker/zentao/zentaopms:/www/zentaopms \ -v /opt/docker/zentao/mysql:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD数据库密码\ -d easysoft/zentao:1…

Reset复位电路的PCB布局布线要求

Reset复位电路的PCB布局布线要求 —来源&#xff1a;瑞芯微RK3588 PCB设计白皮书 Reset复位电路是一种用来使电路恢复到起始状态的电路设计&#xff0c;一般简单的复位电路由电容串阻电阻构成&#xff0c;再复杂点就有三级管等配合进行&#xff0c;RK3588内置复位电路&#xf…

react中hooks分享

一. HOOKS是什么 在计算机程序设计中&#xff0c;钩子一词涵盖了一系列技术&#xff0c;这些技术用来通过拦截函数调用、消息或在软件组件之间传递的事件来改变或增加操作系统、应用程序或其他软件组件的行为。处理这些被截获的函数调用、事件或消息的代码称为“hook”。 在r…

【iOS】锁

线程安全 当一个线程访问数据的时候&#xff0c;其他的线程不能对其进行访问&#xff0c;直到该线程访问完毕。简单来讲就是在同一时刻&#xff0c;对同一个数据操作的线程只有一个。而线程不安全&#xff0c;则是在同一时刻可以有多个线程对该数据进行访问&#xff0c;从而得…

LeetCode--剑指Offer75(1)

目录 题目描述&#xff1a;剑指 Offer 05. 替换空格&#xff08;简单&#xff09;题目接口解题思路1代码解题思路2代码 PS: 题目描述&#xff1a;剑指 Offer 05. 替换空格&#xff08;简单&#xff09; 请实现一个函数&#xff0c;把字符串 s 中的每个空格替换成"%20&quo…

Qt Creator中designer使用QWebEngine异常排查

Qt Creator中designer使用QWebEngine异常排查 1、前提背景 最近由于版权的原因&#xff0c;我们采取了自编译的Qt Creator。编译完成之后启动Qt Creator刚开始一切都是很顺利。 但是在Creator中打开designer&#xff0c;使用QWebEngine控件就发生了异常&#xff0c;Qt Creat…

新一代图像合成模型:Stable Diffusion XL(SDXL)上线!

几个使用Stable Diffusion XL 1.0生成的图像示例。 新的SDXL 1.0发布允许在本地计算机上运行的高分辨率人工智能图像合成。 周三&#xff0c;Stability AI发布了其下一代开源权重人工智能图像合成模型Stable Diffusion XL 1.0&#xff08;SDXL&#xff09;。它可以根据文本描述…