☰
二叉树入门:遍历、递归与层序搜索
2026/10/8 18:37:15 网站建设 项目流程

摘要

二叉树是树形数据结构的基础模型,搜索树、堆、语法树和文件目录等结构都可以从树的思想延伸出来。二叉树的核心学习内容包括节点、左右子树、递归遍历和层序遍历。

本文从二叉树的基本概念开始,介绍前序、中序、后序和层序遍历,并使用 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=None

2. 构造示例树

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. 为什么树遍历容易写错?

通常是因为没有先明确遍历顺序,或者忽略了空节点。写递归函数前,先定义:

  1. 空树返回什么。
  2. 当前节点什么时候处理。
  3. 子树按什么顺序处理。

2. 递归会不会栈溢出?

如果树退化成很深的链表,递归深度可能超过 Python 限制。数据规模大或树形态不可控时,使用显式栈更安全。

3. 为什么层序遍历要记录当前队列长度?

记录当前长度可以区分不同层。如果只持续取队首,仍然能遍历全部节点,但无法直接得到每层的分组结果。

4. 普通二叉树可以快速查找吗?

不能保证。只有增加排序、平衡或索引等结构约束,才能获得更稳定的查找性能。

5. 删除树节点为什么常用后序遍历?

删除一个节点前先处理子树,可以确保子树资源或状态已经处理完毕。文件目录删除就是一个典型例子。

六、进阶思考

1. 二叉搜索树

二叉搜索树满足:

左子树所有值 < 当前节点 右子树所有值 > 当前节点

在树高较小时,查找、插入和删除接近O(log n);退化后可能降为O(n)。

2. 平衡树

AVL 树、红黑树等结构通过旋转保持树高,避免频繁操作后退化。Python 内置字典不是二叉搜索树,而是哈希结构。

3. 序列化与反序列化

树可以转换为列表或字符串,用于存储、传输和缓存。序列化时要保留空节点信息,否则可能无法还原原始结构。

4. 树与图的关系

树是一种没有环的连通图。学习树的遍历后,可以自然过渡到图的 DFS、BFS、最短路径和拓扑排序。

结论

二叉树通过节点和左右子树表达层级关系。前序、中序、后序属于深度优先遍历,层序遍历使用队列实现广度优先访问。掌握递归定义、显式栈和队列后,就能解决大量树结构问题。

下一篇将介绍排序算法,比较不同排序方法的思想、复杂度、稳定性和适用场景。

参考资料

  1. Python 官方文档:https://docs.python.org/3/
  2. Introduction to Algorithms:https://mitpress.mit.edu/9780262046305/introduction-to-algorithms/
  3. Open Data Structures:https://opendatastructures.org/

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询