[Leetcode] 二叉树的遍历

news2024/11/25 22:30:28

转载自(有删减和少量改动) 图解二叉树的四种遍历 https://leetcode.cn/problems/binary-tree-preorder-traversal/solution/tu-jie-er-cha-shu-de-si-chong-bian-li-by-z1m/

1. 相关题目

144.二叉树的前序遍历 https://leetcode.cn/problems/binary-tree-preorder-traversal/

94. 二叉树的中序遍历 https://leetcode.cn/problems/binary-tree-inorder-traversal

145. 二叉树的后序遍历 https://leetcode.cn/problems/binary-tree-postorder-traversal/submissions/

102. 二叉树的层序遍历 https://leetcode.cn/problems/binary-tree-level-order-traversal/

2. 基本概念

二叉树

首先,二叉树是一个包含节点,以及它的左右孩子的一种数据结构。

遍历方式

如果对每一个节点进行编号,你会用什么方式去遍历每个节点呢?

按照 根节点 -> 左孩子 -> 右孩子 的方式遍历,即「先序遍历」,遍历结果为 1 2 4 5 3 6 7;

按照 左孩子 -> 根节点 -> 右孩子 的方式遍历,即「中序遍历」,遍历结果为 4 2 5 1 6 3 7;

按照 左孩子 -> 右孩子 -> 根节点 的方式遍历,即「后序遍历」,遍历结果为 4 5 2 6 7 3 1;

最后,层次遍历就是按照每一层从左向右的方式进行遍历,遍历结果为 1 2 3 4 5 6 7。

3. 题目解析

这四道题目描述是相似的,给定一个二叉树,让我们使用一个数组来返回遍历结果,首先来看递归解法。

3.1 递归解法

在此只介绍前三种的递归解法。它们的模板相对比较固定,一般都会新增一个 dfs 函数:

def dfs(root):
    if not root:
        return
    res.append(root.val)
    dfs(root.left)
    dfs(root.right)

对于前序、中序和后序遍历,只需将递归函数里的 res.append(root.val) 放在不同位置即可,然后调用这个递归函数。

3.1.1 前序遍历

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        def dfs(root):
            if not root:
                return
            dfs(root.left)
            res.append(root.val)
            dfs(root.right)
        dfs(root)
        return res

3.1.2 中序遍历

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        def dfs(root):
            if not root:
                return
            dfs(root.left)
            res.append(root.val)
            dfs(root.right)
        dfs(root)
        return res

3.1.3 后序遍历

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        def dfs(root):
            if not root:
                return
            dfs(root.left)
            dfs(root.right)
            res.append(root.val)
        dfs(root)
        return res

3.2 迭代解法

3.2.1 前序遍历

3.2.1.1 层层入栈

我们使用栈来进行迭代,过程如下:

初始化栈,并将根节点入栈;

当栈不为空时:

弹出栈顶元素 node,并将值添加到结果中;

如果 node 的右子树非空,将右子树入栈;

如果 node 的左子树非空,将左子树入栈;

由于栈是“先进后出”,所以先将右子树入栈,这样使得前序遍历结果为 “根->左->右”的顺序。

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        stack, res = [root], []
        while stack:
            node = stack.pop()
            if node:
                #根节点的值加入到结果中
                res.append(node.val) 
                #右子树入栈
                if node.right:
                    stack.append(node.right)
                #左子树入栈
                if node.left:
                    stack.append(node.left) 
        return res                    

3.2.1.3 层层入栈(使用标志位)

输出的顺序“根 -> 左 -> 右”,入栈的顺序“右 -> 左 -> 根”。

入栈时额外加入一个标识 flag = 0。每次从栈中弹出元素时,如果 flag 为 0,则需要将 flag 变为 1 并连同该节点再次入栈,只有当 flag 为 1 时才可将该节点加入到结果中。

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        
        stack, res = [(0,root)], []
        
        while stack:
            flag, node = stack.pop()
            #if not node: continue
            if node:
                #遍历过了,加入结果
                if flag == 1:
                    res.append(node.val)
                else:
                    stack.append((0,node.right)) #右
                    stack.append((0,node.left)) #左
                    stack.append((1,node)) #根
        return res

3.2.1.2 根节点和左孩子先入栈

模板解法的思路稍有不同,它先将根节点 cur 和所有的左孩子入栈并加入结果中,直至 cur 为空,用一个 while 循环实现。

然后,每弹出一个栈顶元素 tmp,就到达它的右孩子,再将这个节点当作 cur 重新按上面的步骤来一遍,直至栈为空。这里又需要一个 while 循环。

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        cur, stack, res = root, [], []
        
        while cur or stack:
            while cur:
                #根节点和左孩子入栈
                res.append(cur.val)
                stack.append(cur)
                cur = cur.left

            #每弹出一个元素就达到右孩子
            tmp = stack.pop()
            cur = tmp.right
            
        return res 

3.2.2 中序遍历

3.2.2.1 层层入栈(使用标志位)

和前序遍历的代码类似,区别只是入栈顺序。

输出的顺序“左 -> 根 -> 右”,入栈的顺序“右 -> 根 -> 左”。

入栈时额外加入一个标识 flag = 0,每次从栈中弹出元素时,如果 flag 为 0,则需要将 flag 变为 1 并连同该节点再次入栈,只有当 flag 为 1 时才可将该节点加入到结果中。

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        
        stack, res = [(0,root)], []
        
        while stack:
            flag, node = stack.pop()
            #if not node: continue
            if node:
                #遍历过了,加入结果
                if flag == 1:
                    res.append(node.val)
                else:
                    stack.append((0,node.right)) #右
                    stack.append((1,node)) #根
                    stack.append((0,node.left)) #左
        return res

3.2.2.2 根节点和左孩子先入栈

和前序遍历的代码类似,只是在出栈的时候才将节点 tmp 的值加入到结果中。

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        cur, stack, res = root, [], []
        
        while cur or stack:
            while cur:
                #根节点和左孩子入栈,到达最左端的叶子节点
                stack.append(cur)
                cur = cur.left

            tmp = stack.pop()
            
            #出栈时再加入结果
            res.append(tmp.val)
            cur = tmp.right 

3.2.3 后序遍历

3.2.3.1 层层入栈(使用标志位)

和前序遍历的代码类似,区别只是入栈顺序。

输出的顺序“左 -> 右 -> 根”,入栈的顺序“根 -> 右 -> 左”。

入栈时额外加入一个标识 flag = 0,每次从栈中弹出元素时,如果 flag 为 0,则需要将 flag 变为 1 并连同该节点再次入栈,只有当 flag 为 1 时才可将该节点加入到结果中。

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        
        stack, res = [(0,root)], []
        
        while stack:
            flag, node = stack.pop()
            #if not node: continue
            if node:
                #遍历过了,加入结果
                if flag == 1:
                    res.append(node.val)
                else:
                    stack.append((1,node)) #根
                    stack.append((0,node.right)) #右
                    stack.append((0,node.left)) #左
        return res

3.2.3.2 根节点和左孩子先入栈

节点 cur 先到达最右端的叶子节点并将路径上的节点入栈;

然后每次从栈中弹出一个元素后,cur 到达它的左孩子,并将左孩子看作 cur 继续执行上面的步骤。

最后将结果反向输出即可。

说明:

1.参考前序遍历的实现,可以达到输出 “根 -> 右 -> 左 ”,逆序即为预期输出。

2.后序遍历采用模板解法并没有按照真实的栈操作,而是利用了结果的特点反向输出。

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        cur, stack, res = root, [], []
        
        while cur or stack:
            while cur:
                #先到达最右端
                res.append(cur.val)
                stack.append(cur)
                cur = cur.right
            tmp = stack.pop()
            cur = tmp.left

        return res[::-1]

3.3 层序遍历

二叉树的层序遍历的迭代方法与前面不用,前面的都采用了深度优先搜索的方式,而层次遍历使用了广度优先搜索,广度优先搜索主要使用队列实现。

广度优先搜索的步骤为:

初始化队列 q,并将根节点 root 加入到队列中;

当队列不为空时:

队列中弹出节点 node,加入到结果中;

如果左子树非空,左子树加入队列;

如果右子树非空,右子树加入队列;

题目描述:

由于题目要求每一层保存在一个子数组中,所以用 level 保存每层的遍历结果,并使用 for 循环来实现。

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if not root:
            return []
        res, q = [], [root]
        while q:
            n = len(q)
            level = []
            for i in range(n):
                #从队列中逐个弹出该层的每个节点
                node = q.pop(0)
                level.append(node.val)

                #将节点的左右孩子加到队尾,供下一层遍历使用
                q.append(node.left) if node.left else 1
                q.append(node.right) if node.right else 1
            res.append(level)
        return res

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

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

相关文章

【SpringMVC 入门教程】

SpringMVC_day02 🌈博客主页:屠一乐的博客 📅 发文时间:2023.1.5 🎈 一定存在只有你才能做成的事 🌹 博主水平有限,如有错误,欢迎指正 欢迎各位👍收藏💎评论✉…

MacBookPro安装mysql遇到的几个问题

用Mac的好处是不用开关机,无弹窗无广告,坏处是在安装某些第三方的软件时,总是和视频教程上的winows版不一致,需要自己上网找资料尝试怎么安装。今天学python,需要安装mysql,幸好网上有一些文章,…

Vulnhub靶机:MISDIRECTION_ 1

目录介绍信息收集主机发现主机信息探测网站探测反弹shell方式1:使用nc方式2:使用bash方式3:使用MSF提权sudo提权passwd提权docker提权参考介绍 系列:Misdirection(此系列共1台) 发布日期:2019 …

【ClickHouse】从Mysql迁移到ClickHouse大全

从关系型的数据库(Mysql)升级到列式管理的联机分析型数据库(ClickHouse),这不亚于是小米加步枪升级为加特林机关枪的性能提升了,查询能力等确实是大大的提升了,这出现了一个问题我们之前存储在Mysql里的历史数据怎么往ClickHouse里面迁移呢&a…

访问者模式Visitor

1.意图:表示一个作用于某对象结构中的各元素的操作。它允许在不改变各元素的类的前提下定义作用于这些元素的操作。 2.结构 Visitor(访问者)为该对象结构中ConcreteElement的每一个类声明一个Visit操作。该操作的名字和特征标识了发送Visit请…

本地机器 Google Colab 通过 SSH 连接远程服务器

1. 情景描述 我自己笔记本配置太垃圾,想要用学校的深度学习服务器在Colab上跑程序。 2. 环境描述 远程服务器 (Ubuntu): 用pip安装 jupyter notebook 以及 jupyter_http_over_ws 拓展包 (前提有python环境和pip) pip install notebookpip install j…

Android设计模式详解之外观模式

前言 外观模式也称门面模式,在开发过程中的运用频率非常高; 定义:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行;门面模式提供一个高层次的接口,使得子系统更易于使用; 使用场景&#…

6.3 Docker

目录 6.3.1 Docker概述 6.3.1.1 什么是Docker 6.3.1.2 Docker组成 6.3.2 Docker的安装 6.3.2.1 下载Docker依赖的环境 6.3.2.2 指定Docker镜像源 6.3.2.3 安装Docker 6.3.2.4 启动Docker并测试 6.3.3 Docker的中央仓库 6.3.4 Docker操作 6.3.4.1 镜像操作 6.3.4.…

从url获取参数并转为对象

const getParameters URL > JSON.parse({"${decodeURI(URL.split("?")[1]).replace(/"/g, \\").replace(/&/g, ",").replace(//g, ":")}"})getParameters("https://www.google.com.hk/search?qjsmd&neww…

【深度学习】李宏毅2021/2022春深度学习课程笔记 - Self-supervised Learning(自监督式学习)

文章目录一、芝麻街与进击的巨人二、Self-supervised Learning三、BERT3.1 Masking Input3.2 Next Sentence Prediction3.3 GLUE 任务集3.4 How to use BERT3.4.1 Case13.4.2 Case23.4.3 Case33.4.4 Case43.5 Training BERT is challenging!3.6 Pre-Training a Seq2Seq Model3.…

ERP是什么意思?

“ERP到底是一个怎么样的存在?为何有那么多的方面?如何学习?” 本文从ERP起源讲起,结合制造业离散制造与流程制造的ERP系统区别,详解ERP概念。 文章有点长,但如果你耐心看完,相信你会对ERP有一…

elasticsearch 基本语法(常见的RESTFUL API)

一 . ES的基本语法 文章目录一 . ES的基本语法1.Query String 语法2.Query DSL 语法3. Full-text queries 全文检索4. Phrase search 短语搜索5.Query and filter 查询和过滤6. Compound queries 查询7.HighLight search(高亮显示)测试数据内容:PUT /product/_doc/1…

科研试剂 Dextran-DBCO;葡聚糖-二苯并环辛烯;生物可降解高分子聚合物

DBCO修饰的葡聚糖聚 Dextran-DBCO 葡聚糖-二苯并环辛烯 名称:DBCO修饰葡聚糖 英文名称:Dextran-DBCO 外观状态:白色粉末 溶剂:DMSO等常规有机溶剂。 性状:基于不同的分子量,呈白色/类白色固体粉末&…

python爬虫(一)

一、理论 (1)URL管理器:网页之间的链接很复杂,a指向b,b指向a,如果不对其进行管理则可能导致重复爬取、循环爬取,因此单独用该模块进行管理。URL管理器有两个功能,分别是URL队列管理&…

神策营销云平台化应用实践

营销云是一个业务系统,需要贴合客户的实际业务场景,以平台化 分层设计撬动场景发挥价值主张。本文将详细介绍神策营销云在数字化转型浪潮中的沉淀。点击文末“阅读原文”立即观看完整版演讲回放。一、神策营销云产品理念“营销云是一个业务系统”&#…

规范有效的需求变更管理,分7步走。

1、建立需求基线 需要提前建立需求基线,需求基线是需求变更的依据,并需制定双方皆认可的需求变更流程。 需对用户需求进行明确分析,颗粒度越小越好。基准文件定位范围越详细,双方对需求越清晰,用户交流顺畅&#xff0c…

Go语言context包源码剖析

context包的作用 context包是在go1.7版本中引入到标准库中的 context可以用来在goroutine之间传递上下文信息,相同的context可以传递给运行在不同goroutine中的函数,上下文对于多个goroutine同时使用是安全的,context包定义了上下文类型&am…

两数之和【每日一题】

⭐前言⭐ ※※※大家好!我是同学〖森〗,一名计算机爱好者,今天让我们进入刷题模式。若有错误,请多多指教。更多有趣的代码请移步Gitee 👍 点赞 ⭐ 收藏 📝留言 都是我创作的最大的动力! 合抱之木…

MySQL数据库自动补全工具

MySQL数据库操作起来命令较多,默认无法使用tab键进行补全,所以可以安装一些实用的工具进行日常的操作 方式一:临时使用自动补全功能 mysql -u root -p --auto-rehash 方式二:永久使用自动补全功能 vi /etc/my.cnf [mysql] a…

Qt扫盲-QSqlDatabase理论总结

QSqlDatabase理论总结一、概述二、使用1. 连接数据库2. 数据库驱动3. 自定义数据驱动使用4. 注意事项三、常用的功能一、概述 QSqlDatabase类提供了通过连接访问数据库的接口。QSqlDatabase的一个实例表示一个数据库连接对象。该连接是通过Qt支持的数据库驱动程序来对数据库的…