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

目录

前言

算法思想

非递归实现前序遍历

过程分析

代码

非递归实现中序遍历

过程分析

代码

非递归实现后序遍历

过程分析

代码


前言

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);
                //更新prev
                prev = top;
                st.pop();
            }
            else
            {
                //右树的左路节点入栈
                cur = top->right;
            }
        }
        return v;
    }
};

完~

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

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

相关文章

CSS学习笔记:rem实现移动端适配的原理——媒体查询

移动端适配 移动端即手机端&#xff0c;也称M端 移动端适配&#xff1a;同一套移动端页面在不同屏幕尺寸的手机上可以实现宽度和高度的自适应&#xff0c;也就是页面中元素的宽度和高度可以根据屏幕尺寸的变化等比缩放 rem配合媒体查询可实现移动端适配 rem单位 媒体查询 …

post请求

文章目录 一、get请求和post请求区别二、get请求和post请求的用法对比1.get请求2.post请求 三、如何知道是get请求还是post请求 一、get请求和post请求区别 二者区别就是一句话&#xff1a;post请求更安全 二、get请求和post请求的用法对比 1.get请求 get请求: 请求参数&am…

安泰电子:高压功率放大器应用场合介绍

高压功率放大器是一种电子设备&#xff0c;用于将低电压信号放大到较高电压水平&#xff0c;以满足各种应用需求。它在多个领域中具有广泛的应用&#xff0c;包括科学研究、工业生产、通信技术以及医疗设备。下面安泰电子将介绍高压功率放大器的应用场合。 科学研究 高压功率放…

SpringBoot实现接口防抖的几种方案,杜绝重复提交

插&#xff1a; AI时代&#xff0c;程序员或多或少要了解些人工智能&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家(前言 – 人工智能教程 ) 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家…

YOLOV10阅读总结

GitHub - THU-MIG/yolov10: YOLOv10: Real-Time End-to-End Object Detection YOLOv10 - Ultralytics YOLO Docs https://arxiv.org/pdf/2405.14458 论文地址 最近yolo又出了个yolov10了&#xff0c;不得不感慨CV是真卷&#xff0c;毕竟yolov9也才没多久。记录一下阅读笔记。…

curl请求url正常,通过web端请求就400异常的问题记录

通过抓包发现如上图&#xff0c;有2个authorization header&#xff0c;其中一个是开发人员代码生成的&#xff0c;另一个是web端http请求自己携带的。目标是去除web自己携带的。 解决方法&#xff1a; 生成一个FeignConfig的类&#xff0c;requestInterceptor进行空实现。 Fe…

暑期社会实践即将强势来袭,投稿三下乡文章最强攻略

以热爱充实自我 以笃行丰盈青春 这个盛夏“乡”约 纷纷迈出了社会实践的有力步伐 在展开社会实践的同时 也不要忘记投稿宣传的重要性哦 快快收藏住这份投稿攻略 助力团队展现更多精彩的实践故事! No.1 感悟思想伟力&#xff0c;守好“红色根脉” No.2 循迹“八八战略…

【基于 PyTorch 的 Python 深度学习】9 目标检测与语义分割(2)

前言 文章性质&#xff1a;学习笔记 &#x1f4d6; 学习资料&#xff1a;吴茂贵《 Python 深度学习基于 PyTorch ( 第 2 版 ) 》【ISBN】978-7-111-71880-2 主要内容&#xff1a;根据学习资料撰写的学习笔记&#xff0c;该篇主要介绍了优化候选框的几种方法。 一、优化候选框的…

人工智能超万卡集群的核心设计原则和架构

超万卡集群的核心设计原则和架构 超万卡集群建设方兴未艾,当前主要依托英伟达GPU及其设备。英伟达GPU在大模型训练中表现卓越,但国产AI芯片虽进步显著,性能与生态构建仍存差距。面对诸多挑战,构建技术领先、基于国产生态的超万卡集群,仍需不断突破与创新。 大模型升级至万…

腾讯前端4轮面经分享,期望薪资28K

笔者原来在北京360企业安全工作&#xff0c;当时因为大学四年的学业是在北京完成的&#xff0c;所以就顺势通过校招在北京工作了。但家里是南方的&#xff0c;对南方的饮食和生活习惯更加喜欢一些&#xff0c;所以对深圳广州的公司特别是腾讯觊觎已久&#xff0c;所以就在今年2…

期货学习笔记-斐波那契学习1

斐波那契数列介绍 斐波那契数列是1、1、2、3、5、8、13、21、34、55、89…据说这是数学家莱昂纳多 斐波那契研究兔子繁殖时发现的一个神奇数列&#xff0c;似乎大自然在按照这个数列进行演化&#xff0c;一个斐波那契数字是由该数列相邻的前两个数字相加得到的 在斐波那契交易…

Spark Sql写代码方式(yarn)以及 spark sql整合hive详解

引入部分&#xff1a;通常我们在IDEA中写spark代码中如果设置了loacl参数&#xff0c;基本都是在IDEA本地运行&#xff0c;不会提交到 standalone或yarn上运行&#xff0c;在前几篇文章中写的大多数都是该种形式的spark代码&#xff0c;但也写到了如何将spark代码提交到standal…

大模型微调:Lora

原理图 原理&#xff1a;不改变原始大模型参数&#xff0c;只加入一个类似残差分支&#xff0c;先降纬再升纬&#xff0c;因为模型是过参数化的&#xff0c;它们有更小的内在维度&#xff0c;模型主要依赖于这个低的内在维度&#xff08;low intrinsic dimension&#xff09;去…

基于LLM的优化器评测

基于LLM的优化器评测 求解的目标函数测试结果测试代码测试日志 背景: ​ 很多时候我们需要为系统寻找最优的超参.比如模型训练,推理的量化等.本文尝试将LLM当成优化器,帮忙我们寻找最优的超参. 验证方法: 1.设计一个已知最优解的多项式,该多项式有3个变量(因为3个变量可以通过…

飞睿智能高精度、低功耗测距,无线室内定位UWB芯片如何改变智能家居

在数字化和智能化快速发展的今天&#xff0c;定位技术已经成为我们日常生活中不可或缺的一部分。然而&#xff0c;传统的GPS定位技术在室内环境中往往束手无策&#xff0c;给我们的生活带来了诸多不便。幸运的是&#xff0c;随着科技的不断进步&#xff0c;一种名为UWB&#xf…

符合车规级漏电流检测的磁通门传感器KTD1100

电动车充电桩 在政策出台后&#xff0c;充电桩类产品按要求需装配B端漏电流检测装置。它可以有效防止充电桩等设备中的漏电流对用户造成危害&#xff0c;保障用户的用电安全。其次&#xff0c;它可以促进充电桩等产品的质量提升&#xff0c;提高市场的公平竞争&#xff0c;让消…

汽车合面合壳密封UV胶固化后一般可以耐多少度的高温和低温? 汽车车灯的灯罩如果破损破裂破洞了要怎么修复?

汽车合面合壳密封UV胶固化后一般可以耐多少度的高温和低温? UV胶固化后的耐高温和低温能力取决于具体的UV胶水品牌和型号&#xff0c;以及固化过程中的条件。一般来说&#xff0c;高品质的UV胶水在固化后可以提供较好的耐温性能&#xff0c;但确切的耐温范围需要参考各个厂家提…

24年gdcpc省赛C题

1279:DFS 序 先不考虑多节点,先看着颗二叉树,假设他们的父亲节点是第k个被访问的点,如果先访问左子树,那么得到的结果是a1*ka2*(k1)b1*(2k)b2*(2k1),可以发现,先访问左子树,那么右子树每次的乘以的p值实际上是左子树乘以的p值加上左子树的节点个数,比如a1*k和b1*(2k),如果不看2…

Bookie存储架构源码剖析|得物技术

一、Pulsar存储架构简析 Pulsar作为新一代MQ中间件&#xff0c;在底层架构设计上充分贯彻了存算分离的思想&#xff0c;broker与Bookeeper两个组件独立部署&#xff0c;前者负责流量的调度、聚合、计算&#xff0c;后者负责数据的存储&#xff0c;这也契合了云原生下k8s大行其…

新旅程:类与对象的魔法课堂

&#x1f389;&#x1f389;&#x1f389;欢迎莅临我的博客空间&#xff0c;我是池央&#xff0c;一个对C和数据结构怀有无限热忱的探索者。&#x1f64c; &#x1f338;&#x1f338;&#x1f338;这里是我分享C/C编程、数据结构应用的乐园✨ &#x1f388;&#x1f388;&…