Chapter 06

树与二叉树

层次结构的核心——遍历、BST、堆与字典树的完整指南

树的基本概念

二叉树定义与类型

二叉树每个节点最多有两个子节点(左子节点、右子节点)。

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

# 构建示例树:
#       1
#      / \
#     2   3
#    / \   \
#   4   5   6
root = TreeNode(1,
    TreeNode(2, TreeNode(4), TreeNode(5)),
    TreeNode(3, None,       TreeNode(6)))
类型定义性质
满二叉树每个节点有 0 或 2 个子节点叶节点数 = 内节点数 + 1
完全二叉树除最后一层外其余层全满,最后一层节点靠左可用数组紧凑存储,堆的基础
平衡二叉树左右子树高度差 ≤ 1(对每个节点成立)AVL 树、红黑树,保证 O(log n)
二叉搜索树左子树所有值 < 根值 < 右子树所有值中序遍历是有序序列

树的遍历(重中之重)

树的遍历是面试最高频考点,必须烂熟于心。四种遍历方式,每种都要掌握递归和迭代两种写法。

前序遍历(根 → 左 → 右)

访问顺序:先处理当前节点,再递归左右子树。用于复制树、序列化树。

# 递归版 — 代码简洁,理解遍历顺序
def preorder_recursive(root) -> list:
    if not root:
        return []
    return [root.val] + preorder_recursive(root.left) + preorder_recursive(root.right)

# 迭代版 — 显式用栈模拟递归调用栈
def preorder_iterative(root) -> list:
    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

中序遍历(左 → 根 → 右)

对 BST 做中序遍历,得到的是升序序列。这是 BST 最重要的性质之一。

# 递归版
def inorder_recursive(root) -> list:
    if not root:
        return []
    return inorder_recursive(root.left) + [root.val] + inorder_recursive(root.right)

# 迭代版 — 中序遍历的迭代版是最经典的栈应用
def inorder_iterative(root) -> list:
    result, stack = [], []
    cur = root
    while cur or stack:
        # 一路向左走到底,将沿途节点压栈
        while cur:
            stack.append(cur)
            cur = cur.left
        # 弹出栈顶节点:此时它的左子树已全部访问
        cur = stack.pop()
        result.append(cur.val)   # 访问当前节点(根)
        cur = cur.right          # 转向右子树
    return result

后序遍历(左 → 右 → 根)

访问当前节点前,必须先访问完两棵子树。用于计算目录大小、删除树节点。

# 递归版
def postorder_recursive(root) -> list:
    if not root:
        return []
    return postorder_recursive(root.left) + postorder_recursive(root.right) + [root.val]

# 迭代版 — 技巧:前序改造(根→右→左),再反转结果得(左→右→根)
def postorder_iterative(root) -> list:
    if not root:
        return []
    result, stack = [], [root]
    while stack:
        node = stack.pop()
        result.append(node.val)
        if node.left:              # 注意:和前序相反,先压左后压右
            stack.append(node.left)
        if node.right:
            stack.append(node.right)
    return result[::-1]          # 反转得到后序结果

层序遍历(BFS)

按层次从上到下、从左到右访问节点。使用队列实现。

from collections import deque

# 返回每层节点组成的二维列表
def level_order(root) -> list:
    if not root:
        return []
    result = []
    queue = deque([root])

    while queue:
        level_size = len(queue)    # 当前层的节点数
        level = []

        for _ in range(level_size):
            node = queue.popleft()
            level.append(node.val)
            if node.left:  queue.append(node.left)
            if node.right: queue.append(node.right)

        result.append(level)

    return result

# 对示例树 result = [[1], [2, 3], [4, 5, 6]]
📌

四种遍历记忆法
前序:-左-右 → 先访问自己("我先说")
中序:左--右 → 夹在中间("BST 排序")
后序:左-右- → 最后访问自己("孩子优先")
层序:按层 → 用队列(BFS 思想)

二叉搜索树(BST)

BST 的核心性质:对任意节点,左子树所有值 < 节点值 < 右子树所有值

# BST 查找 — O(h),平衡时 O(log n)
def search_bst(root, target):
    if not root or root.val == target:
        return root
    if target < root.val:
        return search_bst(root.left, target)
    return search_bst(root.right, target)

# BST 插入
def insert_bst(root, val):
    if not root:
        return TreeNode(val)   # 找到插入位置
    if val < root.val:
        root.left = insert_bst(root.left, val)
    elif val > root.val:
        root.right = insert_bst(root.right, val)
    return root   # 值已存在,不重复插入

# BST 删除(最复杂的操作)
def delete_bst(root, key):
    if not root:
        return None
    if key < root.val:
        root.left = delete_bst(root.left, key)
    elif key > root.val:
        root.right = delete_bst(root.right, key)
    else:   # 找到要删除的节点
        if not root.left:  return root.right  # 无左子树
        if not root.right: return root.left   # 无右子树
        # 有两个子树:用右子树最小值(中序后继)替换
        successor = root.right
        while successor.left:
            successor = successor.left
        root.val = successor.val                  # 替换值
        root.right = delete_bst(root.right, successor.val)  # 删除后继
    return root

堆(Heap)

堆是一种特殊的完全二叉树,满足堆性质:最大堆中每个节点的值 ≥ 其子节点的值;最小堆则 ≤。

# 完全二叉树可用数组表示(下标从 0 开始)
# 下标 i 的节点:父节点 (i-1)//2,左子节点 2i+1,右子节点 2i+2

def heapify_down(arr, n, i):
    """将下标 i 的元素向下调整,维护最大堆性质"""
    largest = i
    left = 2 * i + 1
    right = 2 * i + 2
    if left < n and arr[left] > arr[largest]:
        largest = left
    if right < n and arr[right] > arr[largest]:
        largest = right
    if largest != i:
        arr[i], arr[largest] = arr[largest], arr[i]
        heapify_down(arr, n, largest)  # 递归调整

def heap_sort(arr):
    n = len(arr)
    # 建堆:从最后一个非叶节点开始向上调整
    for i in range(n // 2 - 1, -1, -1):
        heapify_down(arr, n, i)
    # 排序:逐步将堆顶(最大值)移到末尾
    for i in range(n - 1, 0, -1):
        arr[0], arr[i] = arr[i], arr[0]  # 堆顶与末尾交换
        heapify_down(arr, i, 0)          # 重新堆化
    return arr
建堆O(n) 堆排序O(n log n) 插入/删除O(log n)

字典树(Trie)

Trie(前缀树)是一种多叉树,专门用于字符串的高效存储和检索。每个节点表示一个字符,从根到某个节点的路径表示一个字符串前缀。

应用:搜索自动补全、IP 路由、拼写检查、词频统计。

class TrieNode:
    def __init__(self):
        self.children = {}    # 字符 → 子节点
        self.is_end = False  # 是否是某个单词的结尾

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word: str) -> None:
        node = self.root
        for ch in word:
            if ch not in node.children:
                node.children[ch] = TrieNode()
            node = node.children[ch]
        node.is_end = True   # 标记单词结尾

    def search(self, word: str) -> bool:
        node = self.root
        for ch in word:
            if ch not in node.children:
                return False
            node = node.children[ch]
        return node.is_end   # 必须是完整单词

    def starts_with(self, prefix: str) -> bool:
        node = self.root
        for ch in prefix:
            if ch not in node.children:
                return False
            node = node.children[ch]
        return True   # 只需到达前缀末尾即可

# 时间复杂度:所有操作均为 O(L),L 为字符串长度

经典题目详解

二叉树最大深度

def max_depth(root) -> int:
    if not root:
        return 0
    return 1 + max(max_depth(root.left), max_depth(root.right))
# 递归思路:"树的深度 = max(左子树深度, 右子树深度) + 1"

对称二叉树

def is_symmetric(root) -> bool:
    def mirror(left, right) -> bool:
        if not left and not right: return True
        if not left or not right:  return False
        return (left.val == right.val
                and mirror(left.left, right.right)
                and mirror(left.right, right.left))
    return mirror(root.left, root.right)

路径总和(是否存在根到叶的路径)

def has_path_sum(root, target: int) -> bool:
    if not root:
        return False
    if not root.left and not root.right:  # 叶节点
        return root.val == target
    remaining = target - root.val
    return has_path_sum(root.left, remaining) or has_path_sum(root.right, remaining)

序列化与反序列化

class Codec:
    def serialize(self, root) -> str:
        """前序遍历,None 用 '#' 表示"""
        if not root:
            return '#'
        return f"{root.val},{self.serialize(root.left)},{self.serialize(root.right)}"

    def deserialize(self, data: str):
        nodes = iter(data.split(','))

        def build():
            val = next(nodes)
            if val == '#':
                return None
            node = TreeNode(int(val))
            node.left = build()
            node.right = build()
            return node

        return build()

树题解题模板
大多数树的题目都遵循同一个框架:
1. 处理基本情况(root 为 None)
2. 递归处理左右子树,得到子问题的答案
3. 利用子问题答案合并出当前节点的答案
关键问题:是前序(先用当前节点信息)还是后序(先得到子树结果)?