[Leetcode] 二叉树的遍历

news/2023/12/8 21:27:49

转载自(有删减和少量改动) 图解二叉树的四种遍历 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:returnres.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:returndfs(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:returndfs(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:returndfs(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: continueif 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.rightreturn 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: continueif 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.lefttmp = 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: continueif 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.righttmp = stack.pop()cur = tmp.leftreturn 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 1q.append(node.right) if node.right else 1res.append(level)return res

http://www.ppmy.cn/news/9842.html

相关文章

【Linux】Linux进程的理解 --- 进程描述符、状态、优先级、切换…

如果不改变自己,就别把跨年搞的和分水岭一样,记住你今年是什么吊样,明年就还会是什么吊样!!! 文章目录一、冯诺依曼体系结构(硬件)二、操作系统(软件)1.操作…

基于yolov5-v7.0开发实践实例分割模型超详细教程

早就看到yolov5终于官方支持实例分割了,着实还是很强大的,开源社区的活跃度始终很高,在我前面的一篇博文中已经在官方v7.0分支发布之后紧跟着就介绍了分割的相关技术,如下: 《YOLOv5系列全新升级——yolov5-v7.0实时实例分割全面集成》 感兴趣的话可以自行移步阅读,后面一…

前端插件的应用

像这种页面四个页面下面的展示格式都一样&#xff0c;这个时候就把公共部分代码抽取出来作为组件使用 直接把中间部分代码赋值过来 <template> <div> <div v-for"(items, index) in ford" :key"index"> <div v-if"items.shopC…

千锋教育+计算机四级网络-计算机网络学习-01

目录 课程链接 最早的广域网 计算机网络发展阶段 计算机网络的定义与要点 英文单词网络术语与解释 计算机网络分类 广域网技术 城域网 局域网 个人局域网 五种基本的网络拓扑结构​ 误码率 电路交换网特点 分组交换 交换方式 TCP/IP协议族 IP协议介绍 TCP协议介绍 …

RabbitMQ通配符模式

&#x1f341;博客主页&#xff1a;&#x1f449;不会压弯的小飞侠 ✨欢迎关注&#xff1a;&#x1f449;点赞&#x1f44d;收藏⭐留言✒ ✨系列专栏&#xff1a;&#x1f449;Linux专栏 &#x1f525;欢迎大佬指正&#xff0c;一起学习&#xff01;一起加油&#xff01; 目录&…

远程文件转MultipartFile,并获取ContentType

远程文件转MultipartFile&#xff0c;并获取ContentType 工作中一场景&#xff0c;需要把fastdfs服务器上的远程文件转化成MultipartFile&#xff0c;用来上传到minio服务器上。 遇到一个问题&#xff0c;需要动态的获取到文凭的ContentType&#xff0c;以确保文件或图片能正…

QT部件透明阴影效果与不规则窗体

透明效果原始效果设置整个窗体透明&#xff0c;调用setWindowOpacity( )方法&#xff0c;传入一个0~1之间的值来表示透明度&#xff1b;1表示不透明&#xff0c;0表示完全透明setWindowOpacity(0.5);//0~1之间设置窗体透明&#xff0c;部件不透明setWindowFlags(Qt::FramelessW…

【云原生进阶之容器】第二章Controller Manager原理2.5节--DeltaFIFO剖析

5 DeltaFIFO DeltaFIFO是K8s中用来存储处理数据的Queue,相较于传统的FIFO,它不仅仅存储了数据保证了先进先出,而且存储有K8s 资源对象的类型,它的作用是保证Reflector和Indexer之间对象同步。其是连接Reflector(生产者)和indexer(消费者)的重要通道。其核心处理流程如下: …

正点原子STM32(基于HAL库)

正点原子B站视频地址&#xff1a;https://www.bilibili.com/video/BV1bv4y1R7dp?p1&vd_sourcecc0e43b449de7e8663ca1f89dd5fea7d 目录单片机简介Cortex-M介绍初识STM32STM32命名规则STM32 选型STM32 设计数据手册最小系统IO 分配STM32启动过程分析启动模式启动文件分析(st…

多线程高级(线程状态、线程池、volatile、原子性、并发工具)

1.线程池 1.1 线程状态介绍 当线程被创建并启动以后&#xff0c;它既不是一启动就进入了执行状态&#xff0c;也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢&#xff1f;Java中的线程 状态被定义在了java.lang.Thread.State…

ubuntu Ad-Hoc组网通信

目录 WIFI通信的多种组网方式 1、AP模式 2、Ad-hoc模式 ubuntu18配置ad-hoc模式 WIFI通信的多种组网方式 1、AP模式 最常用的模式&#xff0c;需要一个节点&#xff08;一般是路由器&#xff09;作为AP&#xff0c;其他节点连接到这个AP产生的wifi网络。通信拓扑是星形&a…

新库上线 | CnOpenData中诚信绿金ESG评级数据

中诚信绿金ESG评级数据 一、数据简介 在碳达峰、碳中和的时代浪潮下&#xff0c;以环境、社会、公司治理为核心的ESG投资理念迅速成为发现资本市场投资机遇、规避投资风险的利器。中诚信绿金在多年信用评级经验的基础上通过建立契合国内政策趋势、信息披露现状、行业发展情况…

【黑马】瑞吉外卖-Day01、02笔记

瑞吉外卖 数据库搭建 表结构 Maven项目 创建Maven项目 编写pom文件 编写配置文件application.yml 创建启动类ReggieApplication.java 前端静态资源的配置 将两个前端静态资源包导入到resource目录下方&#xff0c;由于Spring-MVC默认只能访问static和templete下面的文件…

驱动之设备模型

1. 起源与新方案 1.1 起源 仅devfs&#xff0c;导致开发不方便以及一些功能难以支持 热插拔不支持一些针对所有设备的同意操作&#xff08;如电源管理&#xff09;不能自动mknod用户查看不了设备信息设备信息硬编码&#xff0c;导致驱动代码通用性差&#xff0c;即没有分离设…

字典树基础与应用

字典树&#xff08;Trie) 字典树&#xff08;Trie&#xff09;也叫前缀树&#xff0c;是一种针对字符串进行维护的树。 其中的键通常是字符串&#xff0c;由节点在树中的位置决定&#xff0c;键保存在边而不是在节点 一个节点的所有子孙具有相同的前缀&#xff0c;也就是这个…

C++——命名空间,输入输出,缺省参数

✅<1>主页&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;数据结构——二叉树 &#x1f525;<3>创作者&#xff1a;我的代码爱吃辣 ☂️<4>开发环境&#xff1a;Visual Studio 2022 &#x1f4ac;<5>前言&#xff1a;补充C语言…

MySQL调优-深入理解MySQL事务隔离级别与锁机制

目录 MySQL调优-深入理解MySQL事务隔离级别与锁机制 概述 事务及其ACID属性 (1) 原子性(Atomicity) (2)一致性(Consistent) (3) 隔离性(Isolation) (4) 持久性(Durable) 原子性和一致性有何区别&#xff1f; 并发事务处理带来的问题 更新丢失(Lost Update)或脏写 脏读&…

带你玩转指针——指针进阶(二)

上次我们说到了函数指针&#xff0c;对于函数指针大家还不太清楚的参考&#xff0c;指针进阶&#xff08;一&#xff09;http://t.csdn.cn/z5cjM函数指针数组数组是存放相同类型的空间&#xff0c;前面我们已经学习了指针数组int* arr[10] 每个元素是int*那么我们把函数的地址存…

通信原理 | 基本概念

1 通信及通信系统 通信(Communication)是实现信息和消息传输的过程 通信系统(Communication System)的组成: 实现通信的所有硬件和软件设备、传输媒介以及各种通信协议等 消息(Message)、信息(Information)、信号(Signal)的区别: 消息通常指人的感官能够感受到的…

数据库(tidb、clickhouse、hive)概念笔记

目录 1、有哪些分布式数据库 2、OLAP、OLTP、HTAP 3、TIDB、clickhouse、hive 一、TIDB 1. TiDb 核心特性&#xff1a; 2. TiDb 整体架构&#xff1a; 3.TiDB 存储&#xff1a; 二、clickhouse 三、hive 1.什么是 Hive&#xff1f; 2.Hive 架构和如何运作&#xff1…
最新文章