笔记83:二叉树前中后序遍历(迭代法 + 栈)

题目1:. - 力扣(LeetCode)

题目2:. - 力扣(LeetCode)

题目3:. - 力扣(LeetCode)

注意1:每种遍历方式我都提供了两种方法,带图解的方法为个人尝试编写,并非力扣题解,因此时间和空间复杂度可能并不是最优的,只是记录一下自己当时写这个题的时候的思路;

注意2:还有一种统一迭代法,非常的方便,跟递归法一样,三种遍历方式只需改变一下代码的顺序就行了,逻辑都是一样的;



一、个人方法


前序遍历:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        //个人尝试:迭代法 + 栈 -- 实现前序遍历
        //前序遍历:中左右
        //思路:将右边元素全部放入栈中,直接处理左边和中间的元素
        vector<int> result;
        stack<TreeNode*> my_stack;
        TreeNode* ptr = root;

        //剪枝1:
        if(root == nullptr) return result;
        //先将根节点装进栈中,否则无法进入下面的 while 循环
        my_stack.push(ptr);

        //思路:把每个取出来的节点都当作一个子树的根节点,然后对这个子树进行前序遍历;
        //      对于这个子树而言,首先沿着左侧一路遍历到最底部,所有遇到的右侧节点全部装进栈中,稍后进行遍历;
        //      当遍历完一个子树的左半部分之后,开始从栈中取出右侧元素,把他们当作新的子树的根节点,从新进行只遍历左侧的操作;
        while(my_stack.size() != 0) {
            //获得指向 “新子树的根节点” 的指针
            ptr = my_stack.top();
            my_stack.pop();

            //沿新子树的左侧一直遍历到最底部,则停止遍历
            while(ptr->left != nullptr || ptr->right != nullptr) {
                result.push_back(ptr->val);
                if(ptr->left != nullptr && ptr->right != nullptr) {
                    my_stack.push(ptr->right);
                    ptr = ptr->left;
                }
                else if(ptr->left != nullptr && ptr->right == nullptr) {
                    ptr = ptr->left;
                }
                else if(ptr->left == nullptr && ptr->right != nullptr) {
                    my_stack.push(ptr->right);
                    break;
                }
                else break;
            }

            //剪枝2:针对测试用例3,因为在测试用例3的情况下无法进入 while 循环
            //剪枝3:针对测试用例1,因为当 ptr 在新的一轮循环中指向叶节点的时候,是无法进入 while 循环的,所以只能在外面加;
            if(ptr->left == nullptr && ptr->right == nullptr) result.push_back(ptr->val);
        }

        return result;
    }
};

图解:


中序遍历:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        //个人尝试:迭代法 + 栈 -- 实现中序遍历
        //中序遍历:左中右
        //思路:和前序遍历不同,虽然我们在前序遍历的时候也是沿着树的最左边往下遍历,但是我们是遍历一个元素就把这个元素
        //      加到 result 中,因为添加顺序是中左右,而在沿左侧往下遍历的时候“中”元素都已经被遍历到了,所以直接把他添加
        //      到 result 中即可,只需要把右边元素的指针添加到栈中即可,栈中只放同一种东西,就是右侧分支的指针;
        //思路:但是对于中序遍历,我们的添加顺序是左中右,所以在我们沿着树的最左侧一直往下走的时候,我们经过的节点都是属于“中”,
        //      而他对应的值应该放在“左”元素的后面,所以我们不仅要把“右”元素的指针压入栈中,还要把“中”元素的指针也压入栈中;
        //      这样就造成一个问题,如果栈中“右”和“中”元素交替存在,那么对于一棵只有左分支的二叉树(只有根节点有右分支),这样栈中
        //      的元素就是“右中中中中中”,而不是类似满二叉树那样的“右中右中右中”,这样我们就无法辨认栈中的元素是“右”还是“中”;
        //思路:其实如果针对“中”元素和“右”元素的处理是一样的就还好说,但是对于“中”元素需要将其直接放入 result ,对于“右元素”则
        //      需要将他作为新的子树的根节点,然后沿左侧遍历,所以两个元素不能装在一起;
        //思路:那么我想到了第一个解决思路就是,使用两个栈分别存储“中”和“右”,但是这又造成一个问题,我们无法知道哪两个元素是
        //      连在一起的;
        //思路:因此我使用一个栈,但是栈中每个元素存两个指针,一个指向“中”一个指向“右”;

        TreeNode* ptr = root;
        stack<pair<TreeNode*, TreeNode*>> my_stack;
        vector<int> result;

        //剪枝:
        if(root == nullptr) return result;
        //初始化栈和 ptr 指针,使 ptr 指针最开始的位置指向整个二叉树的最左侧叶节点
        while(ptr != nullptr) {
            my_stack.push(pair<TreeNode*, TreeNode*>(ptr, ptr->right));
            ptr = ptr->left;
        }

        //开始取出栈中的元素,给 result 添加元素
        while(my_stack.size() != 0) {
            pair<TreeNode*, TreeNode*> temp_pair = my_stack.top();
            my_stack.pop();
            ptr = temp_pair.first;
            result.push_back(ptr->val);
            ptr = temp_pair.second;
            if(ptr == nullptr) continue;
            //先沿着新子树的根节点一直遍历到最左侧叶节点
            while(ptr != nullptr) {
                my_stack.push(pair<TreeNode*, TreeNode*>(ptr, ptr->right));
                ptr = ptr->left;
            }
        }

        return result;
    }
};

图解:


后序遍历:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        //个人尝试:迭代法 + 栈 -- 实现后序遍历
        //后序遍历:左右中
        vector<int> result;
        stack<pair<TreeNode*, TreeNode*>> my_stack;
        TreeNode* ptr = root;

        //剪枝:
        if(root == nullptr) return result;
        //初始化栈,将 ptr 指针指向子树的最左侧叶节点
        while(ptr != nullptr) {
            my_stack.push(pair<TreeNode*, TreeNode*>(ptr, ptr->right));
            ptr = ptr->left;
        };

        //思路:后序遍历和中序遍历差不太多,细节中小修改一下就行,也是把“中”和“右”元素封装到一层保存;
        //      因为后序遍历的顺序是左右中,所以我们在将左元素加入后,我们就要取出栈中的元素,去遍历“右”元素
        //      的子树,但是这就存在一个问题,因为栈中的元素弹出后就被删除了,而对这个 pair 元素,里面包含
        //      的“中”元素却没有被加入 result 中,被错过了;
        //      所以这里我想了一个小技巧,就是在取出 pair 元素后,先将“中”元素和空指针重新压入栈中,这样就不
        //      会丢掉这个“中”元素,然后去遍历“右”元素作为根节点的子树,沿着左边一直找到该新子树的最左叶节点;
        while(my_stack.size() != 0) {
            pair<TreeNode*, TreeNode*> temp = my_stack.top();
            my_stack.pop();

            ptr = temp.second;
            if(ptr == nullptr) {
                result.push_back(temp.first->val);
                continue;
            }
            else {
                my_stack.push(pair<TreeNode*, TreeNode*>(temp.first, nullptr));
                while(ptr != nullptr) {
                    my_stack.push(pair<TreeNode*, TreeNode*>(ptr, ptr->right));
                    ptr = ptr->left;
                }
            }
        }

        return result;
    }
};

图解:



二、统一迭代法


前序遍历:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        //自己尝试实现【统一迭代法】
        vector<int> result;
        stack<TreeNode*> my_stack;
        if(root == nullptr) return result;

        my_stack.push(root);
        while(my_stack.size() != 0) {
            TreeNode* ptr = my_stack.top();
            my_stack.pop();
            if(ptr != nullptr) {
                if(ptr->right != nullptr) my_stack.push(ptr->right);
                if(ptr->left != nullptr) my_stack.push(ptr->left);
                my_stack.push(ptr);
                my_stack.push(nullptr);
            }
            else {
                TreeNode* temp = my_stack.top();
                my_stack.pop();
                result.push_back(temp->val);
            }
        }

        return result;
    }
};

//思路解析:将“中”元素入栈之后,在后面接上空指针,这样当元素出栈的时候读取到空,就知道空指针后面那一个元素
//          需要加入 result 中;
//          正因为空指针在栈中起到一个标识符的作用,即标志着他后面的那个元素是“中”,因此我们就不能随便往
//          栈里面丢入空指针了,因此当我们在读到叶节点的时候,因为他的两个子节点都是空节点,如果要入栈的话
//          就是入栈两个空指针,这样会导致空指针的标识符作用发生改变,即空指针后面可能不再是“中”元素,因此
//          我们需要【if(ptr->right != nullptr)】和【if(ptr->left != nullptr)】语句来限制叶节点的空子
//          节点随便入栈;

//总结一点:【统一迭代法】给 result 增加元素的时候,必须保证是“中”节点,才会将节点值赋值给 result 数组;
//          不管是前序遍历/中序遍历/后序遍历都是如此,只将“中”节点的值塞进 result 中;

中序遍历:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        //自己尝试实现【统一迭代法】
        vector<int> result;
        stack<TreeNode*> my_stack;
        if(root == nullptr) return result;

        my_stack.push(root);
        while(my_stack.size() != 0) {
            TreeNode* ptr = my_stack.top();
            my_stack.pop();
            if(ptr != nullptr) {
                if(ptr->right != nullptr) my_stack.push(ptr->right);
                my_stack.push(ptr);
                my_stack.push(nullptr);
                if(ptr->left != nullptr) my_stack.push(ptr->left);
            }
            else {
                TreeNode* temp = my_stack.top();
                my_stack.pop();
                result.push_back(temp->val);
            }
        }

        return result;
    }
};

//思路讲解:非常巧妙的方法,因为对“中”和“右”元素的处理是不同的,对“中”元素要直接加入 result 中,对“右”元素
//          要将其视为一棵新的子树进行遍历,因此如果将“中”和“右”元素都放入同一个栈中,就无法区分这两种元素,
//          因此无法对症下药,对不同属性的元素采取不同的处理方式;
//解决方案:因此当我们遇到“中”元素的时候,先把“中”元素压入栈中,然后再压入一个空指针,这样我们读取到空指针的
//          的时候,就知道了这个空指针之后的那个元素要被放入 result 中;对于叶子节点,叶子节点相当于有两个
//          空节点,所以在读取到叶子节点后面挂的俩空节点时,这个叶子节点也就是“中”元素了,然后再在后面入栈
//          一个空节点,那么叶子节点在栈中也变成了前面有一个空指针的“中”元素了,也可以正常的加入到 result 中了;

后序遍历:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        //自己尝试实现【统一迭代法】
        vector<int> result;
        stack<TreeNode*> my_stack;
        if(root == nullptr) return result;

        my_stack.push(root);
        while(my_stack.size() != 0) {
            TreeNode* ptr = my_stack.top();
            my_stack.pop();
            if(ptr != nullptr) {
                my_stack.push(ptr);
                my_stack.push(nullptr);
                if(ptr->right != nullptr) my_stack.push(ptr->right);
                if(ptr->left != nullptr) my_stack.push(ptr->left);
            }
            else {
                TreeNode* temp = my_stack.top();
                my_stack.pop();
                result.push_back(temp->val);
            }
        }

        return result;
    }
};

总结:关于统一迭代法的思路

  • 只有“中节点”才能被加入到数组 result 中;
  • 叶节点也属于一种特殊的“中节点”,相当于有两个空的子节点,因此叶子节点也可以当作“中节点”压入栈中,实现写入 result 的目的;
  • 在将“左节点”,“中节点”,“右节点”压入栈中的时候,要在压入“中节点”之后再压入一个空指针 nullptr 作为标识符,当栈弹出空指针的时候,我们就知道栈当前的头部元素是一个“中节点”而不是“左节点”或者“右节点”,就可以对这个节点元素进行插入数组 result 的操作;
  • 因为我们需要空指针作为“中节点”的标识符,所以当我们读取到叶节点的时候,不能直接将其两个子节点(空节点)压入栈中,否则空指针的标识符作用会受影响;

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

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

相关文章

技术周刊的转变:如何平衡热爱与现实?

大家好&#xff0c;我是那个自己打脸自己的猫哥&#xff0c;本来说周刊不做订阅制的&#xff0c;现在却推出了订阅专栏。今天想为自己辩护一下&#xff0c;同时聊聊技术周刊今后的发展计划。 首先回顾一下我过去的想法吧&#xff0c;然后再解释为什么会突然出现转变。 出于对…

Elasticsearch中父子文档的关联:利用Join类型赋予文档的层级关系

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! Elasticsearch是一个强大的搜索引擎&#xff0c;它提供了丰富的功能来满足复杂的搜索需求。其中&#xff0c;父子索引类型的join功…

伺服系统中电机磁极偏角自学习的实现方案

一、 电机磁极偏角自学习原理简述 要知道磁极偏角&#xff0c;首先要明确的是磁极角&#xff0c;在我个人的理解里磁极角就是park和Ipark变换里所需的电角度&#xff0c;我们的矢量控制方法是定磁链的&#xff0c;就是要保证两相同步旋转坐标系的Id轴和三相静止坐标系的A轴要重…

自定义多数据源

多数据源 第一章 自定义多数据源 文章目录 多数据源前言一、先在配置文件中配置好多个数据源二、配置数据源的配置文件三、定义动态数据源配置1、自定义了Datasource&#xff0c;主要目的是为了在Spring容器中定义一个datasource的Bean&#xff0c;用于mybtais获取数据库连接使…

kali工具----网络映射器(Network Mapper)

识别活跃的主机 尝试渗透测试之前&#xff0c;必须先识别在这个目标网络内活跃的主机。在一个目标网络内&#xff0c;最简单的方法将是执行ping命令。当然&#xff0c;它可能被一个主机拒绝&#xff0c;也可能被接收。本节将介绍使用Nmap工具识别活跃的主机。 1、网络映射器工具…

【迅为iTOP-4412-linux 系统制作(4)】ADB 或者 TF 卡烧写测试

准备工作 编译生成的内核镜像uImage 和设备树 dtb 文件“exynos4412-itop-elite.dtb”已经可以使用了。 把编译生成的uimage和dtb文件。拷贝fastboot工具。官方的u-boot-iTOP-4412.bin 也拷贝到 platform-tools 文件夹目录内。system.img 也拷贝到 platform-tools 文件夹目录…

【Java EE】 IoC详解(Bean的存储)

文章目录 &#x1f38d;Controller&#xff08;控制器存储&#xff09;&#x1f338;如何从Spring容器中获取对象&#xff08;ApplicationContext&#xff09;&#x1f338;获取bean对象的其他方式&#xff08;BeanFactory&#xff09;&#x1f338;Bean 命名约定&#x1f338;…

[ROS 系列学习教程] 建模与仿真 - Gazebo 与 URDF 建模介绍

ROS 系列学习教程(总目录) 本文目录 一、Gazebo 介绍二、URDF 建模介绍2.1 一个简单的实体2.2 rivz显示URDF模型2.3 保存与加载rviz配置2.4 launch文件快速启动2.5 package结构 由于种种原因&#xff0c;有时我们不能直接使用真实的机器人进行调试&#xff0c;这时就需要对机器…

Tomcat源码解析——源码环境搭建

一、源码下载 在进行源码阅读前&#xff0c;先下载源码包&#xff0c;这样便于做笔记和debug。 我所用的版本是Tomcat7.0.68&#xff0c; Tomcat7.0.68下载地址&#xff1a;Index of /dist/tomcat/tomcat-7/v7.0.68/src 所有Tomcat的源码包下载地址&#xff1a;Index of /dist/…

【C++庖丁解牛】哈希表/散列表的设计原理 | 哈希函数

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 前言1.哈希概念2.哈希冲突…

每日一题(力扣)---从中序与后序遍历序列构造二叉树

思路 根据中序遍历和后序遍历的特性可知&#xff0c;后序遍历的最后一个元素为根元素。然后找到中序遍历中对应的序号。将中序遍历的划分为两部分&#xff0c;左边为左子树&#xff0c;右边为右子树。 方法 由思路可知&#xff0c;可以使用递归。递归函数的入口为划分的区间…

day57 判断子序列 不同的子序列 两个字符串的删除操作 编辑距离

题目1 392 判读子序列 题目链接 392 判断子序列 题意 判断字符串s是否为字符串t的子序列 &#xff08;子序列的相对位置在原字符串中不改变&#xff09; 就是求最长公共子序列的长度与字符串s的长度是否相等 动态规划 1&#xff09;确定dp数组及下标i的含义 dp[i][j]…

二十款好用的屏幕录制,绿色绿色好用软件工具,云盘下载

本人收藏多年的屏幕录制工具&#xff0c;绿色的&#xff0c;你懂得的。。。。 二十款好用的屏幕录制&#xff0c;绿色绿色好用软件工具&#xff0c;值得收藏 下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1RPTlFfeap4TGMnDPgCEo-w?pwdmaky 提取码&#xff1…

C#简单工厂模式的实现

using System.Diagnostics.Metrics; using System.Runtime.InteropServices; using static 手写工厂模式.Program;namespace 手写工厂模式 {internal class Program{public interface eats {void eat();}//定义了一个接口public class rice : eats{public void eat() {Console.…

【Next】动态路由、加载 UI 和流式传输

动态路由 动态段作为 params 属性传递给 layout、page、route 和 generateMetadata 函数。 /app/blog/[slug]/page.tsx export default function Page({params}: {params:{slug:string}}) {return <h1>Slug Page -- {params.slug}</h1> };/app/shop/[...slug]/pa…

【C++成长记】C++入门 | 类和对象(上) |类的作用域、类的实例化、类的对象大小的计算、类成员函数的this指针

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;C❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、类的作用域 二、类的实例化 三、类对象模型 四、this指针 1、this指针的引出 2 this指针的特…

4.8-4.12算法刷题笔记

刷题 堆1. 堆排序2. 模拟堆 哈希表3. 模拟散列表4. 字符串哈希 DFS5. 排列数字6. n-皇后问题 2. BFS&#xff08;队列&#xff09;7. 字母迷宫8. 滑动谜题 3. 树与图的dfs9. 树的重心 4. 树与图的bfs(最短路)10. 图中点的层次( 无权最短路 ) 5. 拓扑排序11. 课程表 6. 朴素dijk…

C++系列-C++前言

什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序&#xff0c;对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要高度的抽象和建模时&#xff0c;C语言则不合适&#xff0c;为了解决软件危机&#xff0c;20世纪80年代&#xff0c;计算机界提出…

iterations迭代列表

今天总结一下这个列表的迭代 情况1&#xff0c;两个列表的迭代 import itertoolsfor x1,x2 in itertools.product([1, 5, 8], [0.5, 4]):print((x1,x2))结果如下 情况2&#xff08;一个列表的迭代&#xff09; import itertools# for x1,x2 in itertools.product([1, 5, 8…

大数据产品有哪些分类?各类里知名大数据产品都有哪些?

随着互联网技术的持续进步和全球数字化转型的推进&#xff0c;我们正处于一个数据爆炸的时代。在这样的大背景下&#xff0c;大数据已经逐渐崭露头角&#xff0c;成为了推动各行各业发展的关键因素和核心资源。大数据不仅仅是指数据的规模巨大&#xff0c;更重要的是它蕴含的价…