一、递归的核心思想
递归(Recursion) 是一种通过 函数调用自身 来解决问题的编程技巧。它将一个大问题分解为更小的、结构相似的子问题,直到达到可以直接解决的简单情况(称为 基线条件)。
🌰 经典例子:计算阶乘
- 问题:计算
n! = n × (n-1) × ... × 1
- 递归思路:
def factorial(n): if n == 1: # 基线条件 return 1 else: return n * factorial(n-1) # 递推关系
- 关键点:
n!
的问题被分解为计算(n-1)!
,直到n=1
时直接返回结果。
二、递归的三要素
任何递归都必须包含以下三个部分,缺一不可:
1. 基线条件(Base Case)
- 作用:终止递归的出口,防止无限循环。
- 示例:在阶乘中,
n == 1
时返回1
。
2. 递推关系(Recurrence Relation)
- 作用:将大问题转化为更小的同类问题。
- 示例:
factorial(n) = n * factorial(n-1)
。
3. 递归调用(Self Call)
- 作用:函数内部调用自身处理子问题。
- 注意:每次调用必须向基线条件靠近。
三、递归的执行过程
理解递归的关键是 模拟调用栈(Call Stack) 的行为。每次递归调用会将当前状态压入栈,直到遇到基线条件后逐层返回。
🌰 以 factorial(3)
为例:
- 调用栈展开:
factorial(3) → 3 * factorial(2) → 2 * factorial(1) → 返回 1 ← 2 * 1 = 2 ← 3 * 2 = 6
- 最终结果:
6
四、递归的优缺点
✅ 优点
- 代码简洁:适合处理树形结构(如文件系统遍历、DOM树)。
- 逻辑清晰:如分治算法(快速排序、归并排序)。
❌ 缺点
- 栈溢出风险:深度递归可能导致栈溢出(Stack Overflow)。
- 重复计算:如斐波那契数列递归会重复计算子问题。
🛠️ 优化方法
- 尾递归优化:某些编译器能优化尾递归(如函数最后一步是递归调用)。
- 记忆化(Memoization):缓存已计算的结果(如用装饰器
@lru_cache
)。
五、递归 vs 迭代
递归 | 迭代 |
---|---|
依赖函数调用自身 | 使用循环结构 |
代码简洁但可能低效 | 通常效率更高 |
适合分治问题 | 适合简单重复问题 |
🌰 斐波那契数列对比
# 递归(低效,O(2^n))
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
# 迭代(高效,O(n))
def fib_iter(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
六、递归的典型应用场景
- 树/图遍历:如二叉树的先序、中序、后序遍历。
- 回溯算法:如八皇后问题、数独求解。
- 分治算法:如归并排序、快速排序。
- 动态规划:如背包问题(需结合记忆化)。
七、如何写出正确的递归?
- 明确基线条件:确保递归最终能终止。
- 缩小问题规模:每次递归必须更接近基线条件。
- 假设子问题已解决:相信递归能正确处理更小的问题(类似数学归纳法)。