递推是一种利用循环结构来重复计算的方法。在递推中,问题的每个后继状态都是从前一个状态直接推导出来的,整个过程中不涉及多个分支的自我调用。递推的核心在于找到一个递推公式,通过公式从已知状态逐步推导出目标状态。
递归是一种函数自我调用的过程,用来解决分而治之的问题。递归函数通过调用自身的同名函数来达到重复计算的目的,并且每次调用时问题规模都有所减小,直至达到边界条件(基案)。递归的关键在于将原问题拆分为更小的同类子问题。
递推的实现方式:
for 或 while 循环)递归的实现方式:
递推的性能特点:
递归的性能特点:
递推适用场景: 适合于那些可以直接从一个状态推到下一个状态的问题,如斐波那契数列的计算、简单的数列求和等。
递归适用场景: 适合于解决复杂的问题,如快速排序、归并排序、搜索算法等,这些问题可以自然地分解成更小的子问题。
| 对比项 | 递推 | 递归 |
|---|---|---|
| 计算方式 | 使用循环 | 函数自己调用自己 |
| 使用问题 | 有递推公式的问题 | 把问题规模逐渐缩小的问题 |
| 解决顺序 | 从前到后 | 从大到小 |
| 常见题型 | 数列计算、贪心问题、动态规划 | 数列计算、不知道具体次数的枚举、分治法应用、搜索算法 |
| 难点 | 寻找递推公式 | 子问题拆分 |
以求 F(6) 为例,递推公式为 F(n) = F(n-1) + F(n-2),初始值 F(1)=1, F(2)=1:
| 步骤 | 计算 | 结果 |
|---|---|---|
| 初始 | F(1)=1, F(2)=1 | - |
| n=3 | F(3) = F(2) + F(1) = 1 + 1 | F(3) = 2 |
| n=4 | F(4) = F(3) + F(2) = 2 + 1 | F(4) = 3 |
| n=5 | F(5) = F(4) + F(3) = 3 + 2 | F(5) = 5 |
| n=6 | F(6) = F(5) + F(4) = 5 + 3 | F(6) = 8 |
从前到后,每步只依赖前两步的结果,空间上只需要保存两个变量。
同样求 F(6),递归的调用树为:
1F(6)
2├── F(5)
3│ ├── F(4)
4│ │ ├── F(3)
5│ │ │ ├── F(2) → 1
6│ │ │ └── F(1) → 1
7│ │ └── F(2) → 1
8│ └── F(3)
9│ ├── F(2) → 1
10│ └── F(1) → 1
11└── F(4)
12 ├── F(3)
13 │ ├── F(2) → 1
14 │ └── F(1) → 1
15 └── F(2) → 1注意 F(4) 被计算了 2 次,F(3) 被计算了 3 次,F(2) 被计算了 5 次。这就是递归的重复计算问题,总调用次数随 n 呈指数增长。
| n | 递推计算次数 | 递归调用次数 |
|---|---|---|
| 10 | 9 | 109 |
| 20 | 19 | 13529 |
| 30 | 29 | 约167万 |
| 40 | 39 | 约2亿 |
可以看出,对于相同的问题,朴素递归的效率远低于递推。但递归可以通过记忆化技术优化到与递推相同的效率。
遇到一个需要"从已知推未知"的问题时:
