一、核心概念梳理
(一)递推算法
- 定义:从基础初始条件出发,通过固定的递推关系式正向推进,逐步推导到目标结果的算法,无需调用函数自身。
- 核心特征:"从小到大" 推导,依赖明确的初始条件和递推公式,逻辑直观,计算过程可逐步验证。
(二)递归算法
- 定义:将复杂大问题拆解为规模更小的同类子问题,通过函数自身调用逐步简化问题,直到触及终止条件(最小问题),再回溯得到最终答案的算法。
- 核心特征:"从大到小" 拆解,必须包含终止条件(避免无限递归崩溃)和自我调用逻辑,代码简洁但需注意重复计算问题。
二、典型案例解析
(一)递推 & 递归综合案例:完整昆虫繁衍问题
-
问题背景:每对成虫过 x 个月产 y 对卵,每对卵要过两个月长成成虫;每个成虫不死,第一个月只有一对成虫,卵长成成虫后的第一个月不产卵(需过 x 个月产卵)。求过 z 个月后共有多少对成虫(输入示例:x=1, y=2, z=8,输出:37)。
-
核心逻辑拆解:
- 状态定义:
adult(n):第 n 个月的成虫数量(对)。egg(n):第 n 个月新产生的卵的数量(对)。
- 关键规则:① 成虫不死,第 n 个月成虫 = 第 n-1 个月成虫 + 第 n-2 个月的卵(卵需 2 个月长成成虫);② 成虫需 "过 x 个月产卵",即第 k 个月的成虫,到第 k+x 个月才开始每月产 y 对卵;③ 初始条件:第 1 个月只有 1 对成虫,无卵。
- 递推 / 递归关系式:
- 成虫递推:
a[i] = a[i-1] + y * a[i-x-2](C 语言数组实现的核心公式); - 成虫递归:
adult(n) = adult(n-1) + egg(n-2); - 卵的计算:
egg(n) = adult(n-x) * y(n>x);n≤x 时,egg(n)=0。
- 成虫递推:
- 状态定义:
-
标准答案代码:
#include<iostream>
using namespace std;
int main()
{
// 定义数组存储每月成虫数,z最大为50,数组设为55足够
long long a[55];
int x, y, z;
// 输入x(产卵等待月数)、y(每对产卵数)、z(目标月份)
cin >> x >> y >> z;
// 初始条件:前x+1个月只有1对成虫(未到产卵期,无新成虫)
for (int i = 0; i <= x + 1; i++) {
a[i] = 1;
}
// 从x+2月开始递推计算,直到目标月份z
for (int i = x + 2; i <= z+1; i++) {
// 核心递推公式:当前月成虫 = 上月成虫 + 可产卵成虫产的卵长成的新成虫
// a[i-x-2]是x+2个月前的成虫数(这些成虫产的卵经过2个月长成成虫)
a[i] = a[i - 1] + y * a[i - x - 2];
}
// 输出第z个月的成虫数
cout << a[z];
return 0;
} -
代码测试与说明:
- 输入:1 2 8 → 输出:37(完全匹配题目要求);
- 数组
a的索引对应月份逻辑:a[0]为辅助位,a[1]对应第 1 个月,a[z]直接对应第 z 个月; - 用
long long类型避免数值溢出(z=50 时数值会很大); - 初始循环
i <= x+1:覆盖前 x+1 个月的初始条件,确保无新成虫产生;
(二)递归例题:斐波那契数列(n < 40)
-
题目要求:编写函数计算斐波那契数列第 n 项,数列规则:f1=f2=1;fn=fn-1+fn-2 (n≥3),输入 n 输出对应值(示例:输入 7,输出 13)。
-
递归思路:
- 终止条件:n=1 或 n=2 时,返回 1(数列初始值);
- 递归关系式:n≥3 时,
f(n) = f(n-1) + f(n-2),将求第 n 项拆解为求前两项之和。
-
标准答案代码(C 语言版):
#include<stdio.h>
int f(int n){
// 终止条件:f1=f2=1
if(n==1||n==2){
return 1;
}
else{
// 递归关系式:fn = fn-1 + fn-2
return f(n-1)+f(n-2);
}
}
int main(){
int n;
// 输入目标项数n
scanf("%d",&n);
// 调用递归函数并输出结果
printf("%d",f(n));
return 0;
} -
代码测试:
- 输入:7 → 输出:13(验证:斐波那契数列前 7 项为 1,1,2,3,5,8,13);
- 输入:10 → 输出:55;
- 输入:3 → 输出:2。
-
优化版(C 语言记忆化递归,解决 n 接近 40 时效率低问题):
#include<stdio.h>
// 定义记忆数组,存储已计算结果(n<40,数组大小设为40即可)
int memo[40] = {0};int f_optimized(int n){
// 终止条件
if(n==1||n==2){
return 1;
}
// 已计算过直接返回,避免重复递归
if(memo[n] != 0){
return memo[n];
}
// 未计算则递归求解并存储结果
memo[n] = f_optimized(n-1) + f_optimized(n-2);
return memo[n];
}int main(){
int n;
scanf("%d",&n);
printf("%d",f_optimized(n));
return 0;
}
(三)递归案例 2:集合划分问题
- 问题背景:将 n 个有标号的球放入 k 个无标号的盒子,求不同的划分方法数(球不同、盒相同,与贝尔数区分)。
- 终止条件:
- n=0 或 k=0:划分方法数为 0(无球或无盒)。
- n>k:划分方法数为 0(球数多于盒数,无法每个球占一个盒且盒无标号)。
- n=k:划分方法数为 1(一个球对应一个盒,仅一种排列)。
- 递归关系式(n>k 时):
- 情况 1:将一个球单独放入一个新盒,剩余 n-1 个球放入 k-1 个盒,即 f (n-1, k-1)。
- 情况 2:将一个球放入已有球的任意盒,剩余 n-1 个球放入 k 个盒,因盒无标号但球有标号,需乘 k(对应 k 个可选盒子),即 k*f (n-1, k)。
- 总关系式:f (n, k) = f (n-1, k-1) + k*f (n-1, k)。
- 代码实现:
cpp
#include<bits/stdc++.h>
using namespace std;
int S(int n, int k) {
if (n < k) {
return 0;
}
else if (k == 1) {
return 1;
}
else if (n == k) {
return 1;
}
else {
return S(n-1, k-1) + S(n-1, k) * k;
}
}
int main() {
int n, m;
cin >> n >> m;
int q = S(n, m);
cout << q;
return 0;
}
- 代码说明:
- 先判断终止条件,再执行递归拆解;
- 测试用例中 4 个球放入 2 个盒,最终得到 7 种划分方法,符合逻辑推导结果。
三、递推与递归的核心区别
| 对比维度 | 递推 | 递归 |
|---|---|---|
| 推导方向 | 正向推进(从小到大) | 反向拆解(从大到小) |
| 核心依赖 | 初始条件 + 递推公式 | 终止条件 + 自我调用 |
| 代码结构 | 循环为主,逻辑直白(如 C++ 昆虫繁衍的数组循环) | 函数调用自身,代码简洁 |
| 时间复杂度 | 通常较低(无重复计算) | 暴力递归较高,优化后可降低 |
| 适用场景 | 边界清晰、递推关系明确的问题(如昆虫繁衍、数列) | 问题可拆解为同类子问题的场景(如集合划分) |
| 语言适配 | C/C++ 数组递推更高效(避免递归栈开销) | Python 递归更易读(语法简洁) |
四、学习关键要点
- 递推算法:重点在于找准 "初始条件" 和 "递推关系式",昆虫繁衍中
a[i] = a[i-1] + y*a[i-x-2]是核心,需理解数组索引与月份的对应关系。 - 递归算法:
- 必须先明确终止条件,否则会导致栈溢出、程序崩溃。
- 警惕重复计算问题,可通过记忆化存储(如斐波那契的 memo 数组)优化,尤其适用于 n 较大的场景。
- 拆解问题时需保证子问题与原问题本质一致,避免拆解逻辑偏差。
- 算法选择:简单递推问题优先用递推(如昆虫繁衍用 C++ 数组循环,效率更高),复杂拆解类问题用递归(代码更简洁)。