C++ :借助栈完成二叉树的非递归遍历

news2024/11/9 6:26:47

二叉树的传统访问分为:前序、中序、后序、层序。

其中前三者是递归访问,但是递归是有缺陷的,树太深就会栈溢出。

因此本文我们思考如何使用非递归的方法来完成遍历。

1. 前序遍历   

   要迭代⾮递归实现⼆叉树前序遍历,⾸先还是要借助递归的类似的思想,只是需要把结点存在栈中。
               
思考一下操作系统的栈的物理结构是如何进行递归的:
先进4,4再进2,2再进nullptr,nullptr退栈到2,2进栈到7....
7退出到4,4再进栈到5.......

我们使用数据结构的栈来模仿函数栈帧,将访问分为:

                                        

也就是全部都遵循以下简图访问逻辑:

              

每一个圈可以是一个节点、也可以是一棵树。

可以结合操作系统的栈的工作原理思考为什么这么做。

实现代码:

首先,无论如何我们都要先把根开始的左子树走完。

                   

然后我们严格遵循我们的遍历规则:

1.先访问左路节点

2.访问左路节点的右节点

走出这个循环时,cur已经来到了最左边叶子结点的左(nullptr),  现在应该去访问最左边叶子节点的右了(也就是进入第二步),那么直接从st中取一个出来即可。

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

        while(cur || !st.empty()){
            while(cur){
                ans.push_back(cur->val);//此句表示完成当前节点的遍历
                st.push(cur);
                cur=cur->left;
            }
            //此时cur一定以及是nullptr了,我们可以一个一个拿出刚刚入的栈,去访问右节点了。
            TreeNode* top = st.top();
            st.pop();
            //进行第二步,进入左路节点的右子树
            cur = top->right;
        } 
    return ans; 
    }
};

进入栈顶节点的右之后,进入下一轮循环。将这个top->right当作新的一个递归子问题去再度解决,只不过用的是循环语句。

至于是否进入的条件,要么cur现在已经拿到了一个top->right,还要继续访问;要么栈里还有元素,还能让栈pop一次后再获得新的cur,再进入一轮循环。


 2. 中序遍历

    中序遍历和前序遍历的思考极其相似:更改ans的push_back时间即可(也就是改变完成遍历的时间)。

vector<int> inorderTraversal(TreeNode* root) {
        TreeNode* cur = root;
        stack<TreeNode*> st;
        vector<int> ans;

        while(cur || !st.empty()){
            while(cur){
                //ans.push_back(cur->val);
                st.push(cur);
                cur=cur->left;
            }
            //此时cur一定以及是nullptr了,我们可以一个一个拿出刚刚入的栈,去访问右节点了。
            TreeNode* top = st.top();
            ans.push_back(st.top()->val);
            st.pop();

            cur = top->right;
        } 
    return ans; 
    }

“遍历”是一个抽象的概念,我们认为现在的遍历就是:将val加入到vector中去。

但是为了能让一开始的经过的更靠近根的节点入栈,还是需要把他们“经历”一遍。

但是遍历(也就是进入vector)这个动作,我们需要放到后面pop的地方,这样才是中序。


3. 后序遍历 

相对于前序和中序,后序遍历比较麻烦。

大思路不变:任然是将一个树分为左路节点+左路节点的右子树

用刚才的思路观察后序遍历: 

先和中序一样,一路走到2,将所有的节点都压入栈(但是不能push_back到vector中,与中序同理),然后cur已经走到空了,该从栈中取数据了。

但是取到2的时候不能将2弹出去,因为后序需要先遍历右树。所以进入7,并且将7入栈。

当7完成访问之后,此时才能将2弹出。

因此,一个节点能否被push_back进vector然后再弹出栈,取决于该节点的右子树是否已经完成了遍历。“完成了遍历”也应该有两种情况,一种是右树被遍历完了,比如已经遍历了7之后的2;另一种是右树为空,可以直接弹出,比如7。

现在的问题是,怎么判断右子树是否被访问过或者右子树为空呢?

 为空倒是好判断:

根据后序的访问顺序:左子树 右子树 根

对于每一个节点,最后被访问的都是自己(根),如果根的右子树还没被访问,那上一个被访问的一定是左子树的根。如果根的右子树已经被遍历了,那上一个被遍历的数据一定是右子树的根。

使用双指针法判断上一个被遍历的节点是左子树的根还是右子树的根

此时,top更像是顶替了我们预计的cur的作用, 表示了当前希望被遍历的节点。

完整代码:

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

        while(cur!=nullptr||!st.empty()){
            while(cur){
                st.push(cur);
                cur = cur ->left;
            }
            //走到这的时候cur一定已经是空了
            TreeNode* top = st.top();//先取值,但是不要着急把该节点从栈中弹出来
            if(top->right==nullptr || prev == top->right){//此时就可以访问这个节点了。
                ans.push_back(top->val);
                prev = top;
                st.pop(); 
            }else{
                cur = top->right;
            }
        }
        return ans;
    }
};

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

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

相关文章

【计算机组成原理】实验一:运算器输入锁存器数据写实验

目录 实验要求 实验目的 主要集成电路芯片及其逻辑功能 实验原理 实验内容及步骤 实验内容 思考题 实验要求 利用CP226实验箱上的K16&#xff5e;K23二进制拨动开关作为DBUS数据输入端&#xff0c;其它开关作为控制信号的输入端&#xff0c;将通过K16&#xff5e;K23设定…

无人经济已经 next level 了吗?

01 从无人售货机开始… 晚上 11 点下班回到小区&#xff0c;顺便去驿站取个快递&#xff0c;走进驿站发现四周空无一人&#xff0c;把快递放在机器上滴一声就可以走人了。走的时候在旁边的无人超市里拿一袋方便面&#xff0c;当做加班的安慰……发现了吗&#xff0c;无人门店…

ret2dl_resolve

前言&#xff1a; ret2dl_resolve 是一种利用漏洞进行攻击的技术&#xff0c;主要针对使用动态链接库的程序。它的核心原理是利用程序的重定位机制&#xff0c;通过构造特定的函数返回地址&#xff0c;来劫持控制流并执行攻击者选择的代码。以下是对 ret2dl_resolve 原理的详细…

谷歌地图 | Navigation SDK 重磅发布!为你的 App 注入导航新体验

9月17日&#xff0c;Google 地图正式发布 Navigation SDK for Android 和 iOS&#xff01;借助 Navigation SDK&#xff0c;开发者们现在可以更轻松地为用户打造定制化的导航体验&#xff0c;提升用户满意度&#xff0c;增强用户粘性。无论是界面风格、路线规划还是实时交通信息…

城市酷选:如何四年做到3000亿销售额 会员超500w

城市酷选&#xff0c;这一融合了线上线下消费的会员制社交电商平台&#xff0c;正以其独特的运营模式在市场中崭露头角。该平台不仅汇聚了超过600万的会员与60万商家&#xff0c;更实现了年交易额的百亿突破&#xff0c;彰显了其强大的市场影响力和消费者吸引力。 创新排队免单…

C#基础(14)冒泡排序

前言 其实到上一节结构体我们就已经将c#的基础知识点大概讲完&#xff0c;接下来我们会讲解一些关于算法相关的东西。 我们一样来问一下gpt吧&#xff1a; Q:解释算法 A: 算法是一组有序的逻辑步骤&#xff0c;用于解决特定问题或执行特定任务。它可以是一个计算过程、一个…

FileLink跨网文件传输 | 跨越网络边界的利器,文件传输不再受限

在当今数字化时代&#xff0c;企业与个人对文件传输的需求不断增长&#xff0c;尤其是在跨网环境中。传统的文件传输方式常常受到网络带宽、传输速度和安全性的限制&#xff0c;给用户带来了诸多不便。FileLink 的出现&#xff0c;为这一难题提供了完美解决方案&#xff0c;让文…

理解Web3:去中心化互联网的基础概念

随着科技的不断进步&#xff0c;互联网的形态也在不断演变。从最初的静态网页&#xff08;Web1&#xff09;到动态的社交网络&#xff08;Web2&#xff09;&#xff0c;如今我们正步入一个新的阶段——Web3。这一新兴概念不仅代表了一种技术革新&#xff0c;更是一种互联网使用…

RocketMQ简介与应用场景

简介 RocketMQ是一个由阿里巴巴开源并捐献给Apache的分布式消息中间件&#xff0c;具有高吞吐、低延迟、海量消息堆积等特点&#xff0c;广泛应用于各种分布式系统和大规模数据处理场景。 核心特征 1、高吞吐与低延迟&#xff1a;RocketMQ支持极高的消息吞吐量和极低的消息延…

优思学院|ABC成本方法与精益管理

传统企业计算成本主要基于直接费用。其次的间接费用只需根据某项标准&#xff08;作业时间等&#xff09;&#xff0c;粗略地将费用分配给各种产品即可。 近来&#xff0c;生产线自动化与间接业务高度复杂化&#xff0c;间接费用在制造成本中的比重越来越高&#xff0c;传统的…

netty编程之那么多的网络框架为啥非选你?

写在前面 java nio框架不止一种&#xff0c;为啥非选netty&#xff1f;本文来看下。 1&#xff1a;正文 网络io框架&#xff0c;除了netty外&#xff0c;还有mina&#xff0c;sun grizzly&#xff0c;cindy等&#xff0c;为啥独选netty。 mina netty和mina作者同属一人&…

【计算机视觉】YoloV8-训练与测试教程

✨ Blog’s 主页: 白乐天_ξ( ✿&#xff1e;◡❛) &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山冈&#xff01; &#x1f4ab; 欢迎来到我的学习笔记&#xff01; 制作数据集 Labelme 数据集 数据集选用自己标注的&#xff0c;可参考以下&#xff1a…

用ArcMap实现可视域分析

在 ArcToolbox>>3D Analyst>>可见性>>视域&#xff0c;输入值如图所示&#xff1a; 设置完成后点击确认&#xff0c;生成可视域分析图层 Viewshe1&#xff0c;由内容列表 可见&#xff0c;红色为不可见&#xff0c;绿色为可见。 改变观察点的高度&#xff1a…

pycharm下载selenium等软件包时提示下载超时

1.问题描述 我今天在pycharm运行刚写的自动化脚本时&#xff0c;提示selenium模块未导入&#xff08;自动到导入&#xff09;&#xff0c;鼠标移动到【from selenium import webdriver]的selenium时&#xff0c;显示【未存在文档】 2 解决办法 文件--设置--项目&#xff1a;当前…

企业智能培训新方案,高效打造金牌员工

标品市场竞争激烈&#xff0c;小微企业因长期专注于非标业务或者偏定制化路线&#xff0c;在团队专业能力与大型企业间存在显著差距。专业人才短缺、培养成本高企、培训滞后、效果难测、资源不均、考核标准不一及知识转化率低等问题&#xff0c;成为其业务转型的绊脚石。 如何高…

红外热成像应用场景!

1. 电力行业 设备故障检测&#xff1a;红外热成像仪能够检测电气设备&#xff08;如变压器、电线接头&#xff09;的过热现象&#xff0c;及时发现并定位故障点&#xff0c;预防火灾等安全事故的发生。 水电站查漏&#xff1a;在水电站中&#xff0c;红外热成像仪可用于快速查…

windows自带的录屏功能好用吗?这4款录屏工具也是不错的选择。

因为现在很多人都会有录屏需求&#xff0c;所以平常使用的一些设备当中会有自带的录屏功能。比如windows10系统下只要按下键盘上的 “WinG” 键&#xff0c;就可打开录屏功能。但是录制的时长会有限制&#xff0c;并且录屏功能会有些限制。如果对录屏有更多的需求&#xff0c;可…

网络设备驱动中的调试级别msglevel

网络设备驱动调试级别可以在驱动初始化过程中赋初值&#xff0c;并通过ethtool_ops中.get_msglevel获取&#xff0c;通过.set_msglevel进行设置或修改&#xff0c;并通过如netif_msg_drv这样的宏函数来在需要打印调试信息时进行判断&#xff0c;为真时输出对应级别的调试信息&a…

QT----基于QML的计时器

赶上了实习的末班车,现在在做QML开发,第一天的学习成果,一个计时器.逻辑挺简单的,纯QML实现,代码在仓库QT-Timer 学习使用c的listmodel 学习使用了如何用c的listmodel来存储数据. 新建一个TImeListModel类继承自QAbstractListModel class TimeListModel : public QAbstrac…

AIGC基础工具-科学计算和数据处理的重要库NumPy(Numerical Python)简介

文章目录 1. NumPy 的核心概念1.1 ndarray&#xff1a;多维数组对象示例代码 2. NumPy 的数据类型 (dtype)示例代码 3. NumPy 的数组创建方法3.1 使用 array() 创建数组3.2 使用 zeros() 和 ones()3.3 使用 arange() 和 linspace()3.4 使用 random 模块生成随机数组 4. NumPy 数…