数据结构 / 05
树与二叉树
什么是树
树是一种非线性的层次数据结构,由节点和边组成。掌握以下基本概念:
| 术语 | 含义 |
|---|---|
| 节点(Node) | 树中的基本单元,存储数据 |
| 根(Root) | 树最顶层的节点,没有父节点 |
| 叶子(Leaf) | 没有子节点的节点 |
| 深度(Depth) | 从根到该节点经过的边数 |
| 高度(Height) | 从该节点到最远叶子的边数;树的高度 = 根的高度 |
| 子树(Subtree) | 以某节点为根的整棵树 |
二叉树的特点:每个节点最多有两个子节点——左子节点和右子节点。面试中绝大多数树的题目都围绕二叉树展开。
二叉树的类型
满二叉树(Full Binary Tree)
每个节点要么有 0 个子节点,要么有 2 个子节点,不存在只有 1 个子节点的情况。
完全二叉树(Complete Binary Tree)
除最后一层外每层都被填满,最后一层的节点全部靠左排列。堆(Heap)就是用完全二叉树实现的。
平衡二叉树(Balanced Binary Tree)
任意节点的左右子树高度差不超过 1。AVL 树是严格的平衡二叉树。
二叉搜索树(BST)
满足以下性质:
- 左子树所有节点的值 < 当前节点的值
- 右子树所有节点的值 > 当前节点的值
- 左右子树也分别是 BST
BST 的中序遍历结果是一个升序序列,这是解题的核心性质。
Python 节点定义
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
LeetCode 中所有二叉树题目都使用这个定义,务必记牢。
遍历方式
前序遍历(根-左-右)
递归写法——最直观:
def preorder(root: TreeNode) -> list[int]:
if not root:
return []
return [root.val] + preorder(root.left) + preorder(root.right)
迭代写法——用栈模拟递归,注意先压右再压左:
def preorder_iterative(root: TreeNode) -> list[int]:
if not root:
return []
stack, res = [root], []
while stack:
node = stack.pop()
res.append(node.val)
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return res
中序遍历(左-根-右)
递归写法:
def inorder(root: TreeNode) -> list[int]:
if not root:
return []
return inorder(root.left) + [root.val] + inorder(root.right)
迭代写法——不断向左深入,弹出时处理节点再转向右子树:
def inorder_iterative(root: TreeNode) -> list[int]:
stack, res = [], []
cur = root
while cur or stack:
while cur:
stack.append(cur)
cur = cur.left
cur = stack.pop()
res.append(cur.val)
cur = cur.right
return res
对 BST 执行中序遍历,得到的就是有序数组——很多 BST 题目的关键突破口。
后序遍历(左-右-根)
递归写法:
def postorder(root: TreeNode) -> list[int]:
if not root:
return []
return postorder(root.left) + postorder(root.right) + [root.val]
迭代写法——巧妙做法:按「根-右-左」入栈,最后反转结果:
def postorder_iterative(root: TreeNode) -> list[int]:
if not root:
return []
stack, res = [root], []
while stack:
node = stack.pop()
res.append(node.val)
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
return res[::-1]
层序遍历(BFS)
使用 deque 逐层处理,是 BFS 在树上的标准应用:
from collections import deque
def level_order(root: TreeNode) -> list[list[int]]:
if not root:
return []
queue = deque([root])
res = []
while queue:
level = []
for _ in range(len(queue)):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
res.append(level)
return res
for _ in range(len(queue)) 这一行是层序遍历的关键——它确保每次 while 循环恰好处理一层。
高频技巧
DFS 递归模板
大量二叉树题目都可以归结为「对每个节点,利用左右子树的结果计算当前结果」。
求最大深度(LC 104):
def max_depth(root: TreeNode) -> int:
if not root:
return 0
return 1 + max(max_depth(root.left), max_depth(root.right))
判断是否对称(LC 101):
def is_symmetric(root: TreeNode) -> bool:
def check(left, right):
if not left and not right:
return True
if not left or not right:
return False
return (left.val == right.val
and check(left.left, right.right)
and check(left.right, right.left))
return check(root.left, root.right) if root else True
路径总和(LC 112):
def has_path_sum(root: TreeNode, target: int) -> bool:
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 操作
查找——利用 BST 性质每次排除一半,时间 O(h):
def search_bst(root: TreeNode, val: int) -> TreeNode:
if not root or root.val == val:
return root
if val < root.val:
return search_bst(root.left, val)
return search_bst(root.right, val)
插入:
def insert_bst(root: TreeNode, val: int) -> TreeNode:
if not root:
return TreeNode(val)
if val < root.val:
root.left = insert_bst(root.left, val)
else:
root.right = insert_bst(root.right, val)
return root
验证 BST(LC 98)——用上下界递归:
def is_valid_bst(root: TreeNode) -> bool:
def validate(node, lo=float('-inf'), hi=float('inf')):
if not node:
return True
if node.val <= lo or node.val >= hi:
return False
return (validate(node.left, lo, node.val)
and validate(node.right, node.val, hi))
return validate(root)
从遍历序列构建树
前序 + 中序重建二叉树(LC 105):
前序的第一个元素是根;在中序中找到根的位置,左侧是左子树,右侧是右子树。用哈希表加速查找。
def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode:
idx_map = {val: i for i, val in enumerate(inorder)}
def helper(pre_left, pre_right, in_left, in_right):
if pre_left > pre_right:
return None
root_val = preorder[pre_left]
root = TreeNode(root_val)
in_root = idx_map[root_val]
left_size = in_root - in_left
root.left = helper(pre_left + 1, pre_left + left_size,
in_left, in_root - 1)
root.right = helper(pre_left + left_size + 1, pre_right,
in_root + 1, in_right)
return root
n = len(preorder)
return helper(0, n - 1, 0, n - 1)
时间复杂度 O(n),空间复杂度 O(n)。
经典题目
按难度分组,建议按顺序刷完:
Easy
| # | 题目 | 关键点 |
|---|---|---|
| 104 | 二叉树的最大深度 | DFS 入门,递归一行解 |
| 226 | 翻转二叉树 | 递归交换左右子树 |
| 101 | 对称二叉树 | 双指针递归比较 |
| 108 | 有序数组转 BST | 取中点为根,递归建树 |
| 543 | 二叉树的直径 | 后序遍历 + 全局变量记录最大值 |
Medium
| # | 题目 | 关键点 |
|---|---|---|
| 102 | 二叉树的层序遍历 | BFS 模板题 |
| 98 | 验证二叉搜索树 | 上下界递归 / 中序遍历判递增 |
| 230 | BST 中第 K 小的元素 | 中序遍历计数 |
| 105 | 从前序与中序遍历构造二叉树 | 哈希 + 递归分治 |
| 236 | 二叉树的最近公共祖先 | 后序遍历,左右子树分别查找 |
| 199 | 二叉树的右视图 | BFS 取每层最后一个 / DFS 优先访问右子树 |
| 114 | 二叉树展开为链表 | 前序遍历 + 原地修改指针 |
Hard
| # | 题目 | 关键点 |
|---|---|---|
| 124 | 二叉树中的最大路径和 | 后序遍历,区分「经过当前节点的路径」和「向上贡献的路径」 |
小结
- 递归是二叉树的核心思维方式:绝大多数题目都可以用「把问题分解到左右子树」来解决。
- 四种遍历务必熟练:前序、中序、后序(DFS)和层序(BFS),递归和迭代写法都要会。
- BST 的中序遍历 = 有序序列:这是 BST 类题目最常用的性质。
- 构建树的题目:抓住「前序/后序确定根,中序确定左右子树范围」的规律。
- 刷题建议:先把 Easy 题目写到闭眼能写,再攻克 Medium,最后挑战 Hard。