首页 > 其他分享 >【Day 11 LeetCode】二叉树的遍历

【Day 11 LeetCode】二叉树的遍历

时间:2025-01-07 19:35:17浏览次数:9  
标签:11 遍历 cur vector right 二叉树 ans root Day

一、二叉树的遍历

二叉树的遍历主要分为深度优先遍历广度优先遍历
深度优先是先往深处走,走到尽头返回;广度优先遍历是一层一层往下遍历。其中,深度优先遍历对应三种顺序,前序、中序、后序遍历,特点也很好记,就是根节点的位置。根节点位于前面就是前序,遍历顺序为 根节点-左子树-右子树;相应的,中序遍历的顺序是 左子树-根节点-右子树,后序遍历的顺序是 左子树-右子树-根节点。
深度优先遍历可以采用递归法或迭代法实现,时空复杂度都是 O ( n ) O(n) O(n)(迭代法需要用栈来记录父节点,递归需要调用系统堆栈,所以空间复杂度是 O ( n ) O(n) O(n))。
还有一种特殊的算法叫做Morris遍历,其思想是将空闲右子树的节点指向父节点,从而不需要记录父节点,空间复杂度为 O ( 1 ) O(1) O(1)。
下面介绍使用递归、迭代和Morris三种方法的深度优先遍历 和 迭代方法的广度优先遍历。

1、深度优先遍历–递归写法

前序遍历

class Solution {
    void dfs(TreeNode* node, vector<int> &ans){
        if(node==nullptr)
            return;
        // 前序  根-左-右
        ans.push_back(node->val);
        dfs(node->left, ans);
        dfs(node->right, ans);
    }
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ans;
        dfs(root, ans);
        return ans;
    }
};

中序遍历

class Solution {
    void dfs(TreeNode* node, vector<int> &ans){
        if(node==nullptr)
            return;
        // 中序  左子树-根-右子树
        dfs(node->left, ans);
        ans.push_back(node->val);
        dfs(node->right, ans);
    }
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        dfs(root, ans);
        return ans;
    }
};

后序遍历

class Solution {
    void dfs(TreeNode* node, vector<int> &ans){
        if(node==nullptr)
            return;
        // 后序  左-右-根
        dfs(node->left, ans);
        dfs(node->right, ans);
        ans.push_back(node->val);
    }
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ans;
        dfs(root, ans);
        return ans;
    }
};

2、深度优先遍历–迭代写法

迭代写法就是要清楚用栈存节点的目的是为了保存根节点,再根据各个顺序的特点在恰当的时机保存节点值。
前序遍历

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> q;
        vector<int> ans;
        while(root || !q.empty()){
            while(root){
                q.push(root);
                ans.push_back(root->val); // 先根节点
                root = root->left;
            }
            root = q.top();q.pop();
            root = root->right;
        }
        return ans;
    }
};

中序遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> q; // 用栈来记录深度遍历的路径
        vector<int> ans;
        while(root || !q.empty()){
            // 找到这条路径的最左叶节点
            while(root){
                q.push(root);
                root = root->left;
            }
            root = q.top();q.pop();
            ans.push_back(root->val);
            root = root->right; // 左-根-右
        }
        return ans;
    }
};

后序遍历
当套用中序遍历的迭代版本模板,我始终没想清楚怎么在该基础上写出后序遍历版本。因为上面中序遍历的栈是记录根节点,而后序遍历是根节点的顺序是在最后,再弹出根节点开始右子树时,等遍历完右子树,根节点信息就已经丢失。
需要巧妙的转换一下,后序遍历顺序是左子树-右子树-根,反转一下就是 根-右子树-左子树。这下就与前序遍历类似了。就可以用一套遍历套路解决三种顺序了

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> q;
        vector<int> ans;
        while(root || !q.empty()){
            while(root){
                q.push(root);
                ans.push_back(root->val); // 先根节点
                root = root->right;
            }
            root = q.top();q.pop();
            root = root->left;
        }
        reverse(ans.begin(), ans.end()); // 反转
        return ans;
    }
};

3、深度优先遍历–Morris写法

Morris方法的思想是将空闲右子树的节点指向父节点,从而不需要开辟额外的空间记录父节点。需要搞清楚Morris的代码是在哪进入左子树、右子树。
前序遍历

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        TreeNode* cur = root;
        vector<int> ans;
        while(cur){
            if(cur->left){
                TreeNode* pre = cur->left;
                while(pre->right && pre->right != cur)
                    pre = pre->right;
                if(pre->right == nullptr){
                    pre->right = cur;
                    ans.push_back(cur->val);
                    cur = cur->left; // 进入左子树
                }else{
                    cur = cur->right;
                    pre->right = nullptr;
                }
            }else{
                ans.push_back(cur->val);
                cur = cur->right; // 进入右子树
            }
        }
        return ans;
    }
};

中序遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        TreeNode* cur = root;
        while(cur){
            if(cur->left){
                TreeNode* pre = cur->left;
                // 找到非空左子树的“前驱节点”
                while(pre->right && pre->right!=cur)
                    pre = pre->right;
                if(pre->right==nullptr){
                    // 让“前驱节点”指向当前节点,本质是 记录该左子树的父节点
                    pre->right = cur;
                    cur = cur->left;
                }else{
                    ans.push_back(cur->val);
                    pre->right = nullptr;
                    cur = cur->right;
                }
            }else{
                ans.push_back(cur->val);
                cur = cur->right;
            }
        }
        return ans;
    }
};

后序遍历
与迭代版本遇到的问题一样,所以先讲前序遍历的版本左右互换,得到顺序为 根-右-左的结果,再将结果反转就得到后序遍历结果。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        TreeNode* cur = root;
        vector<int> ans;
        while(cur){
            if(cur->right){
                TreeNode* pre = cur->right;
                while(pre->left && pre->left != cur)
                    pre = pre->left;
                if(pre->left==nullptr){
                    pre->left = cur;
                    ans.push_back(cur->val);
                    cur = cur->right; // 进入右子树
                }else{
                    cur = cur->left;
                    pre->left = nullptr;
                }
            }else{
                ans.push_back(cur->val);
                cur = cur->left; // 进入右子树
            }
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

4、层序遍历

层序遍历思路比较清晰,使用队列记录每一层的节点。
迭代写法:

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ans;
        if(root==nullptr)
            return ans;
        deque<TreeNode*> q;
        q.push_back(root);
        while(!q.empty()){
            vector<int> tmp;
            int n = q.size();
            while(n--){
                TreeNode* node = q.front();
                tmp.push_back(node->val);
                q.pop_front();
                if(node->left)
                    q.push_back(node->left);
                if(node->right)
                    q.push_back(node->right);
            }
            ans.push_back(tmp);
        }
        return ans;
    }
};

递归写法(通过在递归函数中传入深度参数实现):

class Solution {
    void dfs(TreeNode* node, vector<vector<int>> &ans, int depth){
        if(node==nullptr)
            return;
        if(depth==ans.size())
            ans.push_back(vector<int>());
        ans[depth].push_back(node->val);
        dfs(node->left, ans, depth+1);
        dfs(node->right, ans, depth+1);
    }
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ans;
        dfs(root, ans, 0);
        return ans;
    }
};

二、写在后面

今天完成了二叉树深度优先遍历三种顺序(前序、中序、后序)的三个方法(递归、迭代、Morris)的编写,还有广度优先遍历的递归和迭代写法。具体思路细节之后有时间补充。

标签:11,遍历,cur,vector,right,二叉树,ans,root,Day
From: https://blog.csdn.net/H2020fighting/article/details/144963235

相关文章

  • 【Java教程】Day21-13 Web开发:Web应用的文件结构与静态资源处理
    在开发JavaWeb应用程序时,除了常见的 Servlet 和 Filter 等逻辑组件外,还需要处理诸如 JSP 这样的视图文件和一些静态资源文件,如 CSS、JS 等。合理组织Web应用的文件结构至关重要,它能够提升开发效率,方便后期维护,并确保应用在生产环境中的高效运行。1.Web应用程序......
  • 【Java教程】Day21-01 Web开发:深入HTTP协议与简单HTTP服务器编写
    今天,我们访问网站、使用App时,基本上都基于Web架构,这种架构叫做Browser/Server模式,简称BS架构。BS架构的核心特征在于,客户端仅需一个浏览器,所有应用程序的逻辑和数据都存储在服务器端。客户端通过浏览器发出请求,服务器将Web页面返回并展示给用户。Web页面通常具有很强的交互性,H......
  • 【Java教程】Day20-21 设计模式:行为型模式——策略模式
    1.引言策略模式(StrategyPattern)是一种行为型设计模式,它定义了一系列的算法,将它们封装起来,并使得它们可以相互替换。策略模式的主要目的是使得算法可以独立于使用它的客户而变化。它常用于需要根据不同条件选择不同算法的场景。在Java的标准库中,策略模式得到了广泛的应用,特......
  • 力扣刷题:二叉树OJ篇(下)
    大家好,这里是小编的博客频道小编的博客:就爱学编程很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!!目录1.(1)题目描述(2)解题思路2.对称二叉树(1)题目描述(2)解题思路3.另一棵树的子树(1)题目描述(2)解题思路4.二叉树的构建及遍历(1)题目描述(2......
  • win11一些优化【集合百度上的大佬】
    隐藏资源管理器窗口的主文件夹、图库、OneDrive:注册表编辑器使用Windows+R快捷键打开「运行」对话框,执行regedit打开注册表编辑器。⌈主文件夹⌋注册表路径:计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{f87431......
  • 算法基础 -二叉树遍历
    文章目录1.二叉树概念2.层序遍历2.1.复杂度2.2.示例12.3.示例23.层次遍历23.1.层次遍历规则3.2.层次遍历举例3.3.层次遍历代码4.先序遍历4.1.先序遍历规则4.2.先序遍历举例4.3.先序遍历代码(递归)4.4.先序遍历代码(非递归)5.中序遍历5.1.中序遍历规则5.2.......
  • CICD Day5、Jenkins pipline
    在创建web-demo项目的时候,使用的是freestyleproject自由风格项目类型。此外,jenkins还提供了pipline项目类型(又称流水线),它具有以下特点:基于代码的描述:通过代码描述整个构建过程,pipline脚本可以被存储在代码仓库中进行版本管理。团队成员还可以通过查看脚本来了解整个软件交付......
  • CICD Day4、Jenkins主从架构
    Jenkins主从架构(Master-Slave)是一种分布式架构,主节点负责管理项目配置、任务调度和监控,从节点用于执行具体的构建任务。Jenkins主从架构如下图所示 当项目触发构建时,主节点将任务分配到某个从节点,从节点根据项目配置执行一系列操作,如拉取代、代码编译、部署到目标服务器等......
  • 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-11- 标签页(tab)操作 - 下篇 (详细教
    1.简介本来按照计划这一系列的文章应该介绍Context和Page两个内容的,但是宏哥看了官方文档和查找资料发现其实和宏哥在Python+Playwright系列文章中的大同小异,差不了多少,再在这一个系列介绍就有点画蛇添足,索性就不介绍和讲解了,有兴趣的自己可以看宏哥之前写的,或者自己查找资料和官......
  • 【Rust自学】11.1. 编写和运行测试
    喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)11.1.1.什么是测试在Rust里一个测试就是一个函数,它被用于验证非测试代码的功能是否和预期一致。在一个测试的函数体里通常执行3个操作:准备(Arrange)数据/状态运......