二叉树的遍历大全(前序,中序,后序)+(递归,迭代,Morris方法)

news2024/11/15 20:11:43

文章目录

  • 二叉树的前序遍历
    • 递归
    • 迭代
    • Morris遍历
  • 二叉树的中序遍历
    • 递归
    • 迭代
    • Morris遍历
  • 二叉树的后序遍历
    • 递归
    • 迭代
    • Morris遍历

二叉树的前序遍历

力扣传送门:
https://leetcode.cn/problems/binary-tree-preorder-traversal/description/

递归

递归的解法非常简单,只需要记住这是前序遍历,因此只要记住在访问节点的时候是按照根左右的顺序访问的,因此首先记录根节点,然后递归遍历左子树和右子树。

class Solution {
public:
    vector<int> res;
    void dfs(TreeNode* root)
    {
        if (root==nullptr)
        {
            return;
        }
        res.emplace_back(root->val);
        dfs(root->left);
        dfs(root->right);
    }
    vector<int> preorderTraversal(TreeNode* root) {
        dfs(root);
        return res;
    }
};

迭代

迭代的过程其实就是隐式的维护一个栈。

在这里插入图片描述

写法一: 前序遍历:依次处理根左右
首先不断搜索节点的左孩子,然后记录当前节点的值,这就是前序遍历的访问根节点的过程。搜索到左孩子之后加入到栈中,然后利用栈的先进先出的特性, 栈顶节点就是最左的叶子节点(如果有的话),这就是访问左子树的过程,然后依次弹出每一个栈顶节点,访问其右子节点,这就是访问右子树,然后重复这个循环。

class Solution {
public:
    vector<int> res;
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> sp;
        vector<int> res;
        while (root || !sp.empty())
        {
            while (root)
            {
                res.emplace_back(root->val);
                sp.emplace(root);
                root=root->left;
            }
            root=sp.top();
            sp.pop();
            root=root->right;
        }
        return res;
    }
};

写法二:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        if (root==nullptr)
        {
            return res;
        }
        stack<TreeNode*> sp;
        sp.emplace(root);
        while (!sp.empty())
        {
            TreeNode* node=sp.top();
            sp.pop();
            res.emplace_back(node->val);
            //首先把右子树放入栈中,因为是前序遍历,因此栈中要满足栈顶是左子树,往下才是右子树
            if (node->right)
            {
                sp.emplace(node->right);
            }
            if (node->left)
            {
                sp.emplace(node->left);
            }
        }
        return res;
    }
};

Morris遍历

前序遍历的Morris的过程:
在这里插入图片描述

  • 首先判断当前节点的左子树为空,则说明当前位于叶子节点上,将当前节点放入结果数组中,遍历其右子树

  • 当前节点的左子树不为空,找到中序遍历节点的当前节点的前驱节点,令它为thread(线索节点)。

    • 如果thread的右子树为空,则让thread的右子树指定当前节点,当前节点的值放入到结果数组中(根节点的存储),再将当前节点移动到当前节点的左子树节点
    • 如果thread的右子树不为空,则让当前节点移动到其右子树节点,让thread的右子树指向空。
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        while (root)
        {
        	//左子树不为空
            if (root->left)
            {   
            	//找到线索节点(当前节点的左子树的最右节点,即是中序遍历的前驱节点)
                TreeNode* thread=root->left;
                while (thread->right!=nullptr &&thread->right!=root)
                {
                    thread=thread->right;
                }
                //thread为空,则连接线索
                if (thread->right==nullptr)
                {
                    //建立线索:连接右子树
                    thread->right=root;
                    //在进入左右子树之前,首先存储根节点:前序遍历
                    res.emplace_back(root->val);
                    root=root->left;
                }
                else
                {
                    //左子树遍历完成,断开连接
                    root=root->right;
                    thread->right=nullptr;
                }
            }
            //左子树为空,到达了叶子节点,保存节点的值,根据线索返回
            else
            {
                //到达了叶子节点
                res.emplace_back(root->val);
                root=root->right;
            }
        }
        return res;
    }
};

二叉树的中序遍历

力扣传送门:
https://leetcode.cn/problems/binary-tree-inorder-traversal/description/

递归

递归的解法非常简单,只需要记住这是中序遍历,因此只要记住在访问节点的时候是按照左根右的顺序访问的,因此首先记录左子树,然后记录根节点,最后递归遍历右子树。

class Solution {
public:
    vector<int> res;
    void dfs(TreeNode* root)
    {
        if (root==nullptr)
        {
            return;
        }
     	//左  根   右
        dfs(root->left);
        res.emplace_back(root->val);
        dfs(root->right);
    }
    vector<int> preorderTraversal(TreeNode* root) {
        dfs(root);
        return res;
    }
};

迭代

隐式的维护一个栈。

类似于前序遍历一样,中序遍历我们只需要在其访问根节点的时候,把根节点的节点放入结果数组中即可,可以比较以下 前序和中序遍历时

res.emplace_back(root->val);

这条语句出现的位置。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> sp;
        while (root || !sp.empty())
        {
            while (root)
            {
            	//如果是前序遍历,则res的插入要放在这里(即在左子树和右子树之前)。
                sp.emplace(root);
                root=root->left;
            }
            root=sp.top();
            sp.pop();
            //中序遍历:左根右,根在左子树之后,右子树之前
            res.emplace_back(root->val);
            root=root->right;
        }
        return res;
    }   
};

Morris遍历

中序遍历Morris过程:

  • 首先判断当前节点的左子树为空,则说明当前位于叶子节点上,将当前节点放入结果数组中,遍历其右子树

  • 当前节点的左子树不为空,找到中序遍历节点的当前节点的前驱节点,令它为thread(线索节点)。

    • 如果thread的右子树为空,则让thread的右子树指定当前节点,将当前节点移动到当前节点的左子树节点
    • 如果thread的右子树不为空,则让当前节点移动到其右子树节点,当前节点的值放入到结果数组中(根节点的存储),让thread的右子树指向空。

同样前序和中序的遍历只有一个敌方不同:

 res.emplace_back(root->val);

可以比较一下前序和中序Morris的遍历时这条语句的位置。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        while (root)
        {
            if (root->left)
            {   
                TreeNode* thread=root->left;
                while (thread->right!=nullptr &&thread->right!=root)
                {
                    thread=thread->right;
                }
                if (thread->right==nullptr)
                {
                    //建立线索:连接右子树
                    //前序遍历:res的插入要放在这里
                    thread->right=root;
                    root=root->left;
                }
                else
                {
                    //左子树遍历完成,断开连接
                    //中序遍历: 这条语句放在这里(当前else表示左子树遍历完了,接着要遍历右子树,所以在左右之间,首先存储根节点)
                    res.emplace_back(root->val);	//左右之间:存储根节点
                    root=root->right;	//遍历右子树
                    thread->right=nullptr;
                }
            }
            else
            {
                //到达了叶子节点
                res.emplace_back(root->val);
                root=root->right;
            }
        }
        return res;
    }   
};

二叉树的后序遍历

力扣传送门:
https://leetcode.cn/problems/binary-tree-postorder-traversal/description/

递归

递归的解法非常简单,只需要记住这是后序遍历,因此只要记住在访问节点的时候是按照左右根的顺序访问的,因此首先递归遍历左子树和右子树,最后记录根节点。

class Solution {
public:
    vector<int> res;
    void dfs(TreeNode* root)
    {
        if (root==nullptr)
        {
            return;
        }
        dfs(root->left);
        dfs(root->right);
        res.emplace_back(root->val);
    }
    vector<int> preorderTraversal(TreeNode* root) {
        dfs(root);
        return res;
    }
};

迭代

迭代的过程是隐式的维护一个栈。

方法一:利用一个prev临时节点保存位置

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> sp;
        TreeNode* prev;
        vector<int> res;
        while (root ||!sp.empty())
        {
            while (root)
            {
                sp.emplace(root);
                root=root->left;
            }
            root=sp.top();
            sp.pop();
            if (root->right==nullptr || root->right==prev)
            {
                
                res.emplace_back(root->val);
                prev=root;
                root=nullptr;
            }
            else
            {
                sp.emplace(root);
                root=root->right;
            }
        }
        return res;
    }
};

方法二: 翻转
后序遍历的顺序是 左右根 ,因此我们想要得到 左右根的遍历顺序,可以首先得到 根右左的遍历顺序,然后进行一次翻转(自行证明,画个图即可)根右左又可以看成是前序遍历的 根左右,只需把 左和右子树的遍历互换一下位置即可。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        if (root==nullptr)
        {
            return res;
        }
        stack<TreeNode*> sp;
        sp.emplace(root);
        while (!sp.empty())
        {
            TreeNode* node=sp.top();
            sp.pop();
            //根
            res.emplace_back(node->val);
           	//右:注意这里首先left入栈
            if (node->left)
            {
                sp.emplace(node->left);
            }
            //左:之后让right入栈,想想一下在栈中就会出现栈顶是右子树,往下是左子树
            //因此这里就成功的把根左右变成了  《根右左》
            if (node->right)
            {
                sp.emplace(node->right);
            }
        }
        //根右左 进行一次翻转: 左右跟:后序遍历
        reverse(res.begin(),res.end());
        return res;
    }
};

Morris遍历

后序遍历的Morris比较复杂,因此在这里使用跟迭代的方法二类似的办法:翻转。

后序遍历:左右根,因此首先求出 根右左,然后进行一次翻转即可,根右左又可以看作是根左右的右子树和左子树进行一次互换位置即可,因此就是前序遍历的Morris,把左右子树的遍历过程进行一次调换即可,得到了根右左的序列,最后再进行一次翻转,得到了左右根的后序遍历。

总结: 把前序遍历的Morris过程中的左右互换,最后进行一次翻转。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        while (root)
        {
            if (root->right)
            {   
                TreeNode* thread=root->right;
                while (thread->left!=nullptr &&thread->left!=root)
                {
                    thread=thread->left;
                }
                if (thread->left==nullptr)
                {
                    //建立线索:连接左子树
                    thread->left=root;
                    res.emplace_back(root->val);
                    root=root->right;
                }
                else
                {
                    //右子树遍历完成,断开连接
                    root=root->left;
                    thread->left=nullptr;
                }
            }
            else
            {
                //到达了叶子节点
                res.emplace_back(root->val);
                root=root->left;
            }
        }
        reverse(res.begin(),res.end());
        return res;
    }
};

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

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

相关文章

快过年了,Python实现12306查票以及自动购票....

嗨害大家好鸭&#xff01;我是小熊猫~ 明天就是2023年啦~ 还有谁像我小熊猫一样没有回家的&#xff1f; 这次康康能不能12306抢票回家&#xff01;&#xff01;&#xff01; Python实现12306查票以及自动购票 [代码来源]: 青灯教育-自游老师 [环境使用]: Python 3.8Pycha…

构造函数和原型

1、概述 在典型的 OOP 的语言中&#xff08;如 Java&#xff09;&#xff0c;都存在类的概念&#xff0c;类就是对象的模板&#xff0c;对象就是类的实例&#xff0c;但在 ES6之前&#xff0c; JS 中并没用引入类的概念。ES6&#xff0c; 全称 ECMAScript 6.0 &#xff0c;201…

计算机组成原理实验——三、存储器实验

一、实验目的 1.掌握存储器的工作原理和接口。 2.掌握存储器的实现方法和初始化方法。 3.掌握RISC-V中存储器的存取方式。 二、实验内容 1.利用vivado IP核创建6432的ROM&#xff0c;并在 系数文件中设置数据为123489ab&#xff1b; 2.利用vivado IP核创建6432的RAM&#xf…

猿如意---初学者的一盏明灯---程序员的宝藏app

&#x1f680;write in front&#x1f680; &#x1f4dd;个人主页&#xff1a;认真写博客的夏目浅石. &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​ &#x1f4e3;系列专栏&#xff1a;我的CSDN活动之旅 &#x1f4ac;总结&#xff1a;希望你看完…

Rabbit客户端操作不同交换机[包含延迟类型]

文章目录一&#xff1a;direct-直投交换机0.0: 说明 --- 只有队列和交换机绑定&#xff0c;且routing key路由键一致才会收到消息1.1: 先后创建两个队列1.2: 队列绑定Direct交换机&#xff0c;和routing key1.3: 未指明路由键&#xff1a;1.4: 指明路由键1.5: 两个队列绑定一个…

Python中基本输入和输出

文章目录前言一、使用input()函数输入二、使用print()函数输出前言 从我们接触第一个Python程序开始&#xff0c;就一直在使用 print() 函数向屏幕上输出一些字符&#xff0c;如下图代码所示&#xff1a; print() 函数就是Python的基本输出函数。那既然有输出&#xff0c;肯定…

[牛客复盘] 牛客2022跨年场 20221212

[牛客复盘] 牛客2022跨年场 20221212 一、本周周赛总结二、 A 猜群名1. 题目描述2. 思路分析3. 代码实现三、B 分赃1. 题目描述2. 思路分析3. 代码实现四、C 翻卡牌1. 题目描述2. 思路分析3. 代码实现五、D ygg的分数运算1. 题目描述2. 思路分析3. 代码实现六、E 摇色子1. 题目…

多线程生产者消费者——分别使用条件变量、信号量实现

生产者消费者 概念 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯&#xff0c;而通过阻塞队列来进行通讯&#xff0c;所以生产者生产完数据之后不用等待消费者处理&#xff0c;直接扔给阻塞队列&#xff0c;消费者不…

超好用!win10安装Eiseg标注软件及使用(CPU版本)

写在前面的话 众所周知&#xff0c;标注分割掩膜的软件一般使用labelme&#xff0c;但是一个一个点太麻烦了&#xff0c;工作量太大&#xff0c;&#xff0c;之前&#xff0c;我的思路就是先标少量的数据然后训练个初始模型&#xff0c;再用初始模型对剩下的图像预测掩膜&…

【闲聊杂谈】深入理解Spring Security设计原理

1、什么是Spring Security 顾名思义&#xff0c;Security的意思是安全&#xff0c;本质上就是一个很纯粹的权限管理框架&#xff0c;提供认证和授权两大核心功能。在目前主流的Spring生态中的项目&#xff0c;说到安全框架&#xff0c;基本上SpringSecurity是首选。当然&#…

代码随想录算法训练营第四天|24. 两两交换链表中的节点 、19.删除链表的倒数第N个节点、160.链表相交、142.环形链表II

24. 两两交换链表中的节点 力扣题目链接(opens new window) 解析&#xff1a; 基础题&#xff0c;主要是要把握边界条件&#xff1a;由题可得&#xff0c;交换的节点两两一组&#xff0c;每交换完成一对&#xff0c;问题规模减2&#xff0c;也就是只剩一个或不剩节点时交换便结…

ArcGIS基础实验操作100例--实验28地形图配准

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验28 地形图配准 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&#x…

python中类的使用详解

目录 一.类的定义和使用方法 成员变量和成员方法 成员方法的定义语法和self关键字 小结 二.类和对象 小结 三.属性&#xff08;成员变量&#xff09;的赋值 构造方法&#xff1a;_ _init_ _() 小结 一.类的定义和使用方法 可以使用类去封装属性&#xff0c;并基于类创建…

一文弄懂Pytorch的DataLoader,Dataset,Sampler之间的关系

很多文章都是从DatasetDatasetDataset等对象自下网上进行介绍的&#xff0c;但是对于初学者而言&#xff0c;其实这并不好理解&#xff0c;因为有时候&#xff0c;会不自觉的陷入到一些细枝末节中去&#xff0c;而不能把握重点&#xff0c;所以本文将自上而下的对PytorchPytorc…

HCIP第四天

HCIP实验配置一&#xff0c;实验要求二&#xff0c;172.16.0.0/16地址的划分三&#xff0c;搭建拓扑图四&#xff0c;配置IP地址和环回地址五&#xff0c;宣告并配置缺省路由下放&#xff0c;使用NAT技术六&#xff0c;R5中心站点配置隧道和静态IP七&#xff0c;R6分支站点的配…

canvas在小程序里写小游戏

最近接了个小需求需要写个小游戏&#xff0c;由简单的帧动画加上碰撞相关的处理&#xff0c;组成。具体页面信息如下图 具体的游戏步骤&#xff0c;是通过长按按钮蓄力&#xff0c;松开时卡通人物跳起&#xff0c;卡通人物跳起碰撞到上面的元宝等元素的得分&#xff0c;这里我们…

笔试题之编写SQL分析门店销售情况

销售员、客户、产品 文章目录前言一、SQL题目二、解答方法&#xff08;一&#xff09;建表插入测试数据&#xff08;二&#xff09;第一题解答&#xff08;三&#xff09;第二题解答&#xff08;四&#xff09;第三题解答总结前言 分享本人遇到的笔试真题与解法&#xff0c;并…

MATLAB算法实战应用案例精讲-【人工智能】语义分割(附实战应用案例及代码)

前言 语义分割是一种典型的计算机视觉问题,其涉及将一些原始数据(例如,平面图像)作为输入并将它们转换为具有突出显示的感兴趣区域的掩模。许多人使用术语全像素语义分割(full-pixel semantic segmentation),其中图像中的每个像素根据其所属的感兴趣对象被分配类别ID。…

[ XSS-labs通关宝典 ] xss-labs 通关宝典之 less1 - less5

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

前端常见问题汇总(十)

一、HTTP1.0和HTTP2.0的区别 http1.0&#xff1a;每次请求都需要重新建立tcp连接&#xff0c;请求完后立即断开与服务器连接&#xff0c;这很大程度造成了性能上的缺陷&#xff0c;http1.0被抱怨最多的就是连接无法复用。 http1.1&#xff1a;引入了长连接&#xff08;keep-al…