一、树的本质:递归定义的数据结构
1.1 核心概念
树是层次化的非线性结构。二叉树是每个节点最多有两个子节点的特殊树:
1 ← 根节点(Root)
/ \
2 3 ← 内部节点(Internal)
/ \ \
4 5 6 ← 叶子节点(Leaf)
/
7
术语:
- 深度(Depth):从根到该节点的边数。7的深度=3
- 高度(Height):从该节点到最远叶子的边数。1的高度=3
- 度(Degree):子节点个数。1的度=2,2的度=2
1.2 二叉树的分类
| 类型 | 定义 | 特性 | 应用 |
|---|---|---|---|
| 普通二叉树 | 无特殊约束 | 结构灵活 | 表达式树、文件系统 |
| 满二叉树 | 每层节点数达最大 | 节点数 = 2^h - 1 | 理论分析 |
| 完全二叉树 | 除最后一层外满,最后一层左对齐 | 可用数组存储 | 堆 |
| 二叉搜索树BST | 左 < 根 < 右 | 中序有序 | 动态查找表 |
| 平衡二叉树AVL | 左右高度差 ≤ 1 | 查找O(log n) | 数据库索引 |
二、二叉树的实现与构建
2.1 节点定义
python
class TreeNode:
"""二叉树节点"""
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def __repr__(self):
return str(self.val)
2.2 快速构建测试树
python
def build_tree() -> TreeNode:
"""
构建示例二叉树:
1
/ \
2 3
/ \ \
4 5 6
/
7
"""
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.right = TreeNode(6)
root.left.left.left = TreeNode(7)
return root
三、递归遍历:DFS的三种顺序
3.1 递归三要素(所有递归算法的框架)
写递归函数前必须明确:
- 返回值:函数应该返回什么?(高度?布尔值?节点?)
- 终止条件:什么时候停止递归?(空节点?叶子节点?)
- 递归逻辑:当前层做什么?如何组合子问题的解?
python
def recursion_template(node: TreeNode):
# 1. 终止条件
if not node:
return base_case
# 2. 递归处理子问题
left_result = recursion_template(node.left)
right_result = recursion_template(node.right)
# 3. 组合结果,返回
return combine(node.val, left_result, right_result)
3.2 前序遍历:根-左-右
访问顺序:先处理当前节点,再递归处理左右子树。
python
def preorder(root: TreeNode) -> list[int]:
"""
前序遍历:根-左-右
时间:O(n),空间:O(h) ------ h为树高度(递归栈)
"""
result = []
def dfs(node):
if not node:
return
result.append(node.val) # 访问根
dfs(node.left) # 遍历左
dfs(node.right) # 遍历右
dfs(root)
return result
# 验证
root = build_tree()
print(preorder(root)) # [1, 2, 4, 7, 5, 3, 6]
执行轨迹:
访问1 → 递归左(2) → 访问2 → 递归左(4) → 访问4 → 递归左(7)
访问7 → 7无子节点,返回 → 4无右子节点,返回 → 2递归右(5)
访问5 → 5无子节点,返回 → 1递归右(3) → 访问3 → 递归右(6)
访问6 → 6无子节点,返回
结果:[1, 2, 4, 7, 5, 3, 6]
3.3 中序遍历:左-根-右
BST的中序遍历结果是有序数组------这是BST最重要的性质。
python
def inorder(root: TreeNode) -> list[int]:
"""
中序遍历:左-根-右
BST的中序遍历 = 升序数组
"""
result = []
def dfs(node):
if not node:
return
dfs(node.left)
result.append(node.val)
dfs(node.right)
dfs(root)
return result
# 验证BST性质
bst_root = TreeNode(5)
bst_root.left = TreeNode(3)
bst_root.right = TreeNode(7)
bst_root.left.left = TreeNode(1)
bst_root.left.right = TreeNode(4)
print(inorder(bst_root)) # [1, 3, 4, 5, 7] ✅ 升序
3.4 后序遍历:左-右-根
适用场景:需要先处理子节点再处理父节点的问题(如计算高度、删除树)。
python
def postorder(root: TreeNode) -> list[int]:
"""
后序遍历:左-右-根
"""
result = []
def dfs(node):
if not node:
return
dfs(node.left)
dfs(node.right)
result.append(node.val)
dfs(root)
return result
# 验证
print(postorder(root)) # [7, 4, 5, 2, 6, 3, 1]
四、迭代遍历:用栈模拟递归
递归的本质是函数调用栈。迭代遍历需要手动维护栈来模拟这一过程。
4.1 前序遍历迭代版
思路:栈模拟递归,右子树先入栈(保证左子树先出)。
python
def preorder_iter(root: TreeNode) -> list[int]:
"""
前序遍历迭代版
时间:O(n),空间:O(h)
"""
if not root:
return []
result = []
stack = [root]
while stack:
node = stack.pop()
result.append(node.val)
# 右先入栈,后出 → 左先被访问
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return result
4.2 中序遍历迭代版
思路:一路向左入栈,到底后弹出访问,再转向右。
python
def inorder_iter(root: TreeNode) -> list[int]:
"""
中序遍历迭代版
核心:栈保存待访问的节点,current指向待处理的子树
"""
result = []
stack = []
current = root
while current or stack:
# 阶段1:一路向左,全部入栈
while current:
stack.append(current)
current = current.left
# 阶段2:弹出访问,转向右
current = stack.pop()
result.append(current.val)
current = current.right
return result
执行轨迹 (build_tree()):
| 步骤 | current | stack | 操作 | result |
|---|---|---|---|---|
| 初始 | 1 | [] | 向左入栈 | --- |
| 1 | 2 | [1] | 向左入栈 | --- |
| 2 | 4 | [1,2] | 向左入栈 | --- |
| 3 | 7 | [1,2,4] | 向左入栈 | --- |
| 4 | None | [1,2,4,7] | 弹出7,访问 | [7] |
| 5 | None | [1,2,4] | 弹出4,访问 | [7,4] |
| 6 | None | [1,2] | 弹出2,访问 | [7,4,2] |
| 7 | 5 | [1] | 5入栈 | --- |
| 8 | None | [1,5] | 弹出5,访问 | [7,4,2,5] |
| 9 | None | [1] | 弹出1,访问 | [7,4,2,5,1] |
| 10 | 3 | [] | 3入栈 | --- |
| 11 | None | [3] | 弹出3,访问 | [...,3] |
| 12 | 6 | [] | 6入栈 | --- |
| 13 | None | [6] | 弹出6,访问 | [...,6] |
结果:[7, 4, 2, 5, 1, 3, 6]
4.3 后序遍历迭代版(统一写法)
双栈法(易理解,但空间O(n)):
python
def postorder_iter(root: TreeNode) -> list[int]:
"""
后序遍历:双栈法
栈1:根-右-左,栈2收集,最后逆序
"""
if not root:
return []
stack1 = [root]
stack2 = []
while stack1:
node = stack1.pop()
stack2.append(node.val)
# 先入左,后入右 → 右先被弹出到stack2
if node.left:
stack1.append(node.left)
if node.right:
stack1.append(node.right)
return stack2[::-1] # 逆序:左-右-根
标记法(空间O(h),推荐):
python
def postorder_iter_optimal(root: TreeNode) -> list[int]:
"""
后序遍历:标记法(模拟递归的访问状态)
节点首次入栈为"未访问",再次出栈为"已访问"
"""
if not root:
return []
result = []
stack = [(root, False)] # (节点, 是否已访问)
while stack:
node, visited = stack.pop()
if visited:
result.append(node.val)
else:
# 后序:左-右-根 → 入栈顺序:根-右-左
stack.append((node, True))
if node.right:
stack.append((node.right, False))
if node.left:
stack.append((node.left, False))
return result
4.4 层序遍历:BFS
python
from collections import deque
def level_order(root: TreeNode) -> list[list[int]]:
"""
层序遍历:返回每层的结果
时间:O(n),空间:O(w) ------ w为最大宽度
"""
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return result
# 验证
print(level_order(root)) # [[1], [2, 3], [4, 5, 6], [7]]
五、遍历对比与选择策略
| 遍历方式 | 顺序 | 递归 | 迭代难点 | 典型应用 |
|---|---|---|---|---|
| 前序 | 根-左-右 | 简单 | 右先入栈 | 复制树、序列化 |
| 中序 | 左-根-右 | 简单 | 一路向左 | BST排序、验证BST |
| 后序 | 左-右-根 | 简单 | 标记访问 | 删除树、计算高度 |
| 层序 | 逐层 | 队列 | 记录层大小 | 最短路径、按层处理 |
选择策略:
- 需要父节点信息在子节点之前?→ 前序
- 需要有序结果 或BST验证?→ 中序
- 需要子节点信息在父节点之前?→ 后序
- 需要按层处理 或最短路径?→ 层序
六、经典算法问题
6.1 树的最大深度
python
def max_depth(root: TreeNode) -> int:
"""
最大深度
递归:max(左深度, 右深度) + 1
"""
if not root:
return 0
return max(max_depth(root.left), max_depth(root.right)) + 1
6.2 判断平衡二叉树
优化版:后序遍历,一旦发现不平衡立即返回-1。
python
def is_balanced(root: TreeNode) -> bool:
"""
判断平衡二叉树(左右高度差 ≤ 1)
时间:O(n),空间:O(h)
"""
def check(node):
if not node:
return 0 # 空节点高度0
left_h = check(node.left)
if left_h == -1:
return -1 # 左子树不平衡,提前返回
right_h = check(node.right)
if right_h == -1:
return -1 # 右子树不平衡
if abs(left_h - right_h) > 1:
return -1 # 当前节点不平衡
return max(left_h, right_h) + 1
return check(root) != -1
6.3 最近公共祖先(LCA)
普通二叉树版本(面试重点):
python
def lowest_common_ancestor(root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
"""
最近公共祖先
时间:O(n),空间:O(h)
三种情况:
1. p,q分别在左右子树 → root是LCA
2. p,q都在左子树 → LCA在左
3. p,q都在右子树 → LCA在右
"""
if not root or root == p or root == q:
return root
left = lowest_common_ancestor(root.left, p, q)
right = lowest_common_ancestor(root.right, p, q)
if left and right:
return root # p,q分居两侧
return left if left else right # 同在左侧或右侧
6.4 对称二叉树
python
def is_symmetric(root: TreeNode) -> bool:
"""
判断对称二叉树
时间:O(n),空间:O(h)
"""
def check(left, right):
if not left and not right:
return True
if not left or not right:
return False
if left.val != right.val:
return False
# 外侧相等且内侧相等
return check(left.left, right.right) and check(left.right, right.left)
return not root or check(root.left, root.right)
6.5 路径总和
python
def has_path_sum(root: TreeNode, target: int) -> bool:
"""
是否存在根到叶子的路径和等于target
时间:O(n),空间:O(h)
"""
if not root:
return False
# 叶子节点且值匹配
if not root.left and not root.right:
return root.val == target
# 递归检查子树,目标和减去当前值
return (has_path_sum(root.left, target - root.val) or
has_path_sum(root.right, target - root.val))
七、BST的核心操作
7.1 BST的查找与插入
python
class BST:
"""二叉搜索树"""
def search(self, root: TreeNode, val: int) -> TreeNode:
"""查找:O(h),平衡时O(log n)"""
if not root or root.val == val:
return root
if val < root.val:
return self.search(root.left, val)
return self.search(root.right, val)
def insert(self, root: TreeNode, val: int) -> TreeNode:
"""插入:O(h)"""
if not root:
return TreeNode(val)
if val < root.val:
root.left = self.insert(root.left, val)
elif val > root.val:
root.right = self.insert(root.right, val)
return root
7.2 BST的删除(面试难点)
python
def delete(self, root: TreeNode, val: int) -> TreeNode:
"""删除:O(h)"""
if not root:
return None
if val < root.val:
root.left = self.delete(root.left, val)
elif val > root.val:
root.right = self.delete(root.right, val)
else:
# 找到目标节点
if not root.left:
return root.right
if not root.right:
return root.left
# 有两子节点:用右子树最小值替换
min_node = self._find_min(root.right)
root.val = min_node.val
root.right = self.delete(root.right, min_node.val)
return root
def _find_min(self, node: TreeNode) -> TreeNode:
while node.left:
node = node.left
return node
八、序列化与反序列化
8.1 前序序列化(含None标记)
python
class Codec:
"""
二叉树的序列化与反序列化
使用前序遍历,None标记空节点
"""
def serialize(self, root: TreeNode) -> str:
"""序列化:1,2,4,None,None,5,None,None,3,None,6,None,None"""
if not root:
return "None"
return f"{root.val},{self.serialize(root.left)},{self.serialize(root.right)}"
def deserialize(self, data: str) -> TreeNode:
"""反序列化"""
self.tokens = iter(data.split(","))
def build():
val = next(self.tokens)
if val == "None":
return None
node = TreeNode(int(val))
node.left = build()
node.right = build()
return node
return build()
# 验证
codec = Codec()
s = codec.serialize(root)
print(s) # 1,2,4,7,None,None,None,5,None,None,3,None,6,None,None
new_root = codec.deserialize(s)
print(preorder(new_root)) # [1, 2, 4, 7, 5, 3, 6] ✅
九、树问题决策树
二叉树问题类型判断:
├── 需要按层处理? → 层序遍历(BFS + 队列)
├── 需要有序结果? → 中序遍历(尤其是BST)
├── 需要父节点依赖子节点信息? → 后序遍历
│ └── 高度、平衡性、路径和、子树判断
├── 需要复制/序列化? → 前序遍历
├── 需要找最近公共祖先? → 递归返回节点
├── 需要判断对称/相同? → 递归比较双树
└── 需要路径相关? → 回溯(前序 + 路径记录)
十、核心心法
二叉树问题的解法高度模式化,掌握以下框架可解决 90% 的题目:
1. 递归三要素
写任何递归函数前,先在纸上回答:返回值?终止条件?递归逻辑?
2. 遍历选择口诀
- 父要先用 → 前序
- 子要先用 → 后序
- 要排序 → 中序
- 要分层 → 层序
3. 迭代遍历的统一框架
所有DFS迭代遍历都可以用标记法统一:节点首次入栈标记False,再次出栈(标记True)时访问。
4. BST的利用
看到BST立即想到:中序有序、左小右大、查找/插入/删除O(h)。
判断标准:当你能在看到树题目时,5秒内确定遍历顺序,10秒内写出递归框架,并能在脑中模拟递归栈的展开过程------二叉树才真正成为你的本能。