Python 算法基础篇之树和二叉树

一、树的本质:递归定义的数据结构

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 递归三要素(所有递归算法的框架)

写递归函数前必须明确:

  1. 返回值:函数应该返回什么?(高度?布尔值?节点?)
  2. 终止条件:什么时候停止递归?(空节点?叶子节点?)
  3. 递归逻辑:当前层做什么?如何组合子问题的解?
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秒内写出递归框架,并能在脑中模拟递归栈的展开过程------二叉树才真正成为你的本能。

相关推荐
txzrxz1 小时前
关于前缀和
算法·动态规划·图论
杨连江1 小时前
载流子矩阵限域束缚实现常温常压超导的理论与结构设计
算法
小郑加油1 小时前
python学习Day11:认识与创建CSV文件
开发语言·python·学习
做cv的小昊1 小时前
【TJU】研究生应用统计学课程笔记(6)——第二章 参数估计(2.4 区间估计)
人工智能·笔记·线性代数·算法·机器学习·数学建模·概率论
Pkmer1 小时前
Java程序员大战Python面向对象
python·ai编程
普贤莲花2 小时前
【2026年第18周---写于20260501】---舍得
程序人生·算法·leetcode
2zcode2 小时前
基于深度学习的口腔疾病图像识别系统(UI界面+改进算法+数据集+训练代码)
人工智能·深度学习·算法
小龙报2 小时前
【Coze-AI智能体平台】低代码省时高效:Coze 应用开发全流程指南
java·人工智能·python·深度学习·低代码·chatgpt·交互