很多初学者觉得递归难,是因为试图"一步步跟踪"它的执行过程。正确姿势恰恰相反:递归要求你信任函数本身。本文帮你建立递归的心智模型,并顺势理解它最强大的应用——分治。

1. 什么是递归

递归就是"函数调用自己"。一个递归问题必须包含两部分:

没有基线条件就是无限递归,程序直接栈溢出。

2. 第一个例子:阶乘

def factorial(n):
    if n <= 1:          # 基线条件
        return 1
    return n * factorial(n - 1)  # 缩小规模,信任递归结果

print(factorial(5))   # 120

写递归只问三件事:基线条件对吗?规模缩小了吗?组合逻辑对吗?不要纠结内部执行细节,相信递归已经算好了

3. 递归三要素

  1. 明确职责:函数接收什么、返回什么,一句话说清。
  2. 找出基线:问题最小时怎么处理。
  3. 递推关系:大问题如何由小问题组合而成。

以二叉树遍历为例:遍历一棵树 = 访问根 + 遍历左子树 + 遍历右子树,天然适合递归。

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

def inorder(root):      # 中序遍历:左 → 根 → 右
    if root is None:
        return []
    return inorder(root.left) + [root.val] + inorder(root.right)

root = TreeNode(1)   # 根为 1,左右孩子 2、3
root.left = TreeNode(2)
root.right = TreeNode(3)
print(inorder(root))   # [2, 1, 3]

4. 递归的代价

递归并非免费:每次调用都要压栈,占用 O(深度) 的栈空间;重复子问题还会导致指数级浪费。朴素斐波那契就是经典反例:

def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

# fib(45) 要跑几十秒:同一个子问题被反复计算

from functools import lru_cache

@lru_cache(maxsize=None)
def fib_fast(n):
    if n <= 1:
        return n
    return fib_fast(n - 1) + fib_fast(n - 2)

print(fib_fast(100))  # 瞬间完成

lru_cache记忆化:缓存已算结果,把指数级降到 O(n),也是通往动态规划的桥梁。

5. 分治:递归的杀手级应用

分治三步:分解成互不重叠的子问题 → 递归解决合并结果。快排、归并、二分都是分治,以归并排序为例:

def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)

def merge(a, b):
    i = j = 0
    res = []
    while i < len(a) and j < len(b):
        if a[i] < b[j]:
            res.append(a[i]); i += 1
        else:
            res.append(b[j]); j += 1
    return res + a[i:] + b[j:]

print(merge_sort([5, 1, 4, 2, 8]))  # [1, 2, 4, 5, 8]
💡 学习建议:遇到递归题,先大声说出"职责 + 基线 + 递推"三要素再写代码;平时用"递归转迭代"练习加深理解,面试常考。