摘要
二叉树是树形数据结构的基础模型,搜索树、堆、语法树和文件目录等结构都可以从树的思想延伸出来。二叉树的核心学习内容包括节点、左右子树、递归遍历和层序遍历。
本文从二叉树的基本概念开始,介绍前序、中序、后序和层序遍历,并使用 Python 实现树节点、递归遍历、迭代遍历、最大深度和层序搜索,为后续学习二叉搜索树、堆和图打下基础。
一、背景与问题
数组和链表适合表达线性关系,但很多数据天然具有层级结构:
- 文件夹和文件。
- 组织架构。
- 商品分类。
- HTML 文档结构。
- 编译器语法树。
- 评论回复关系。
如果把层级数据强行放入一个线性列表,查询父子关系需要额外维护索引。树结构通过节点之间的连接直接表达层级。
A / \ B C / \ \ D E F二叉树规定每个节点最多有两个子节点,通常称为左子节点和右子节点。
二、核心概念
1. 节点和边
树由节点和边组成:
- 根节点:树的起点。
- 子节点:由其他节点连接而来的下层节点。
- 父节点:直接连接上层的节点。
- 叶子节点:没有子节点的节点。
- 边:连接两个节点的关系。
2. 深度和高度
- 节点深度:从根节点到该节点经过的边数。
- 树的高度:从根节点到最深叶子节点的最大深度。
- 层:深度相同的节点集合。
这些概念用于分析树的查找、遍历和空间消耗。
3. 二叉树的形态
| 类型 | 特征 |
|---|---|
| 满二叉树 | 每个非叶子节点都有两个子节点 |
| 完全二叉树 | 除最后一层外都填满,最后一层从左到右排列 |
| 平衡二叉树 | 左右子树高度差保持在较小范围 |
| 退化树 | 结构接近链表 |
同样数量的节点,不同形态会产生不同的查询性能。
4. 遍历顺序
树的遍历是访问所有节点的过程:
- 前序:根、左、右。
- 中序:左、根、右。
- 后序:左、右、根。
- 层序:按层从上到下访问。
三、工作原理
1. 深度优先遍历
前序、中序和后序都属于深度优先遍历。它们通常使用递归或显式栈实现。
前序:先处理当前节点,再处理子树 中序:先处理左子树,再处理当前节点 后序:先处理子树,最后处理当前节点递归写法简洁,但本质上仍然依赖调用栈保存待处理的子树。
2. 广度优先遍历
层序遍历使用队列:
访问根节点 → 把根的子节点加入队列 → 取出队首节点 → 加入其子节点 → 重复直到队列为空广度优先适合查找距离根节点最近的目标,或者按层处理数据。
3. 遍历复杂度
设树中有n个节点:
- 时间复杂度:
O(n),每个节点访问一次。 - 递归空间:
O(h),h为树高。 - 层序队列空间:最坏情况下为
O(n)。
平衡树的高度接近log n,退化树的高度可能接近n。
四、实战示例
1. 定义节点
from__future__importannotationsfromdataclassesimportdataclass@dataclassclassTreeNode:value:intleft:TreeNode|None=Noneright:TreeNode|None=None2. 构造示例树
root=TreeNode(1,left=TreeNode(2,left=TreeNode(4),right=TreeNode(5),),right=TreeNode(3,right=TreeNode(6),),)3. 前序遍历
defpreorder(root:TreeNode|None)->list[int]:ifrootisNone:return[]return[root.value,*preorder(root.left),*preorder(root.right),]print(preorder(root))前序结果为[1, 2, 4, 5, 3, 6]。
4. 中序遍历
definorder(root:TreeNode|None)->list[int]:ifrootisNone:return[]return[*inorder(root.left),root.value,*inorder(root.right),]如果二叉树满足二叉搜索树性质,中序遍历会得到有序序列。
5. 后序遍历
defpostorder(root:TreeNode|None)->list[int]:ifrootisNone:return[]return[*postorder(root.left),*postorder(root.right),root.value,]后序遍历适合先处理子树、再处理父节点的场景,例如计算目录大小或删除树结构。
6. 迭代前序遍历
defpreorder_iterative(root:TreeNode|None)->list[int]:ifrootisNone:return[]result:list[int]=[]stack=[root]whilestack:node=stack.pop()result.append(node.value)ifnode.rightisnotNone:stack.append(node.right)ifnode.leftisnotNone:stack.append(node.left)returnresult栈后进先出,因此先压入右子节点,再压入左子节点,才能先处理左子树。
7. 层序遍历
fromcollectionsimportdequedeflevel_order(root:TreeNode|None)->list[list[int]]:ifrootisNone:return[]result:list[list[int]]=[]queue=deque([root])whilequeue:level_values:list[int]=[]for_inrange(len(queue)):node=queue.popleft()level_values.append(node.value)ifnode.leftisnotNone:queue.append(node.left)ifnode.rightisnotNone:queue.append(node.right)result.append(level_values)returnresult结果为[[1], [2, 3], [4, 5, 6]]。
8. 计算树的最大深度
defmax_depth(root:TreeNode|None)->int:ifrootisNone:return0return1+max(max_depth(root.left),max_depth(root.right),)递归定义直接表达了树高:当前节点的高度等于左右子树最大高度加一。
9. 查找目标值
defcontains(root:TreeNode|None,target:int)->bool:ifrootisNone:returnFalseifroot.value==target:returnTruereturncontains(root.left,target)orcontains(root.right,target)普通二叉树没有排序性质,因此最坏需要遍历所有节点。
10. 判断两棵树是否相同
defsame_tree(left:TreeNode|None,right:TreeNode|None,)->bool:ifleftisNoneorrightisNone:returnleftisrightifleft.value!=right.value:returnFalsereturnsame_tree(left.left,right.left)andsame_tree(left.right,right.right,)递归比较结构和节点值,是树问题中常见的分解方式。
五、常见问题与实践建议
1. 为什么树遍历容易写错?
通常是因为没有先明确遍历顺序,或者忽略了空节点。写递归函数前,先定义:
- 空树返回什么。
- 当前节点什么时候处理。
- 子树按什么顺序处理。
2. 递归会不会栈溢出?
如果树退化成很深的链表,递归深度可能超过 Python 限制。数据规模大或树形态不可控时,使用显式栈更安全。
3. 为什么层序遍历要记录当前队列长度?
记录当前长度可以区分不同层。如果只持续取队首,仍然能遍历全部节点,但无法直接得到每层的分组结果。
4. 普通二叉树可以快速查找吗?
不能保证。只有增加排序、平衡或索引等结构约束,才能获得更稳定的查找性能。
5. 删除树节点为什么常用后序遍历?
删除一个节点前先处理子树,可以确保子树资源或状态已经处理完毕。文件目录删除就是一个典型例子。
六、进阶思考
1. 二叉搜索树
二叉搜索树满足:
左子树所有值 < 当前节点 右子树所有值 > 当前节点在树高较小时,查找、插入和删除接近O(log n);退化后可能降为O(n)。
2. 平衡树
AVL 树、红黑树等结构通过旋转保持树高,避免频繁操作后退化。Python 内置字典不是二叉搜索树,而是哈希结构。
3. 序列化与反序列化
树可以转换为列表或字符串,用于存储、传输和缓存。序列化时要保留空节点信息,否则可能无法还原原始结构。
4. 树与图的关系
树是一种没有环的连通图。学习树的遍历后,可以自然过渡到图的 DFS、BFS、最短路径和拓扑排序。
结论
二叉树通过节点和左右子树表达层级关系。前序、中序、后序属于深度优先遍历,层序遍历使用队列实现广度优先访问。掌握递归定义、显式栈和队列后,就能解决大量树结构问题。
下一篇将介绍排序算法,比较不同排序方法的思想、复杂度、稳定性和适用场景。
参考资料
- Python 官方文档:https://docs.python.org/3/
- Introduction to Algorithms:https://mitpress.mit.edu/9780262046305/introduction-to-algorithms/
- Open Data Structures:https://opendatastructures.org/