递推与递归笔记

一、核心概念梳理

(一)递推算法

  • 定义:从基础初始条件出发,通过固定的递推关系式正向推进,逐步推导到目标结果的算法,无需调用函数自身。
  • 核心特征:"从小到大" 推导,依赖明确的初始条件和递推公式,逻辑直观,计算过程可逐步验证。

(二)递归算法

  • 定义:将复杂大问题拆解为规模更小的同类子问题,通过函数自身调用逐步简化问题,直到触及终止条件(最小问题),再回溯得到最终答案的算法。
  • 核心特征:"从大到小" 拆解,必须包含终止条件(避免无限递归崩溃)和自我调用逻辑,代码简洁但需注意重复计算问题。

二、典型案例解析

(一)递推 & 递归综合案例:完整昆虫繁衍问题

  1. 问题背景:每对成虫过 x 个月产 y 对卵,每对卵要过两个月长成成虫;每个成虫不死,第一个月只有一对成虫,卵长成成虫后的第一个月不产卵(需过 x 个月产卵)。求过 z 个月后共有多少对成虫(输入示例:x=1, y=2, z=8,输出:37)。

  2. 核心逻辑拆解:

    • 状态定义:
      • 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
  3. 标准答案代码:

    #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;
    }

  4. 代码测试与说明:

    • 输入:1 2 8 → 输出:37(完全匹配题目要求);
    • 数组a的索引对应月份逻辑:a[0]为辅助位,a[1]对应第 1 个月,a[z]直接对应第 z 个月;
    • long long类型避免数值溢出(z=50 时数值会很大);
    • 初始循环i <= x+1:覆盖前 x+1 个月的初始条件,确保无新成虫产生;

(二)递归例题:斐波那契数列(n < 40)

  1. 题目要求:编写函数计算斐波那契数列第 n 项,数列规则:f1=f2=1;fn=fn-1+fn-2 (n≥3),输入 n 输出对应值(示例:输入 7,输出 13)。

  2. 递归思路:

    • 终止条件:n=1 或 n=2 时,返回 1(数列初始值);
    • 递归关系式:n≥3 时,f(n) = f(n-1) + f(n-2),将求第 n 项拆解为求前两项之和。
  3. 标准答案代码(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;
    }

  4. 代码测试:

    • 输入:7 → 输出:13(验证:斐波那契数列前 7 项为 1,1,2,3,5,8,13);
    • 输入:10 → 输出:55;
    • 输入:3 → 输出:2。
  5. 优化版(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:集合划分问题

  1. 问题背景:将 n 个有标号的球放入 k 个无标号的盒子,求不同的划分方法数(球不同、盒相同,与贝尔数区分)。
  2. 终止条件:
    • n=0 或 k=0:划分方法数为 0(无球或无盒)。
    • n>k:划分方法数为 0(球数多于盒数,无法每个球占一个盒且盒无标号)。
    • n=k:划分方法数为 1(一个球对应一个盒,仅一种排列)。
  3. 递归关系式(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)。
  4. 代码实现:
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;
}
  1. 代码说明:
    • 先判断终止条件,再执行递归拆解;
    • 测试用例中 4 个球放入 2 个盒,最终得到 7 种划分方法,符合逻辑推导结果。

三、递推与递归的核心区别

对比维度 递推 递归
推导方向 正向推进(从小到大) 反向拆解(从大到小)
核心依赖 初始条件 + 递推公式 终止条件 + 自我调用
代码结构 循环为主,逻辑直白(如 C++ 昆虫繁衍的数组循环) 函数调用自身,代码简洁
时间复杂度 通常较低(无重复计算) 暴力递归较高,优化后可降低
适用场景 边界清晰、递推关系明确的问题(如昆虫繁衍、数列) 问题可拆解为同类子问题的场景(如集合划分)
语言适配 C/C++ 数组递推更高效(避免递归栈开销) Python 递归更易读(语法简洁)

四、学习关键要点

  1. 递推算法:重点在于找准 "初始条件" 和 "递推关系式",昆虫繁衍中a[i] = a[i-1] + y*a[i-x-2]是核心,需理解数组索引与月份的对应关系。
  2. 递归算法:
    • 必须先明确终止条件,否则会导致栈溢出、程序崩溃。
    • 警惕重复计算问题,可通过记忆化存储(如斐波那契的 memo 数组)优化,尤其适用于 n 较大的场景。
    • 拆解问题时需保证子问题与原问题本质一致,避免拆解逻辑偏差。
  3. 算法选择:简单递推问题优先用递推(如昆虫繁衍用 C++ 数组循环,效率更高),复杂拆解类问题用递归(代码更简洁)。
相关推荐
lihao lihao2 小时前
c++红黑树
算法
TracyCoder1232 小时前
LeetCode Hot100(1/100)——1. 两数之和 (Two Sum)
算法·leetcode
进击的小头2 小时前
常用数字滤波器的特性与适用场景
c语言·算法
狐573 小时前
2026-01-19-LeetCode刷题笔记-1292-元素和小于等于阈值的正方形的最大边长
笔记·算法·leetcode
张祥6422889043 小时前
误差理论与测量平差基础笔记六
笔记·算法·概率论
mjhcsp4 小时前
透彻背包DP:从DFS暴力搜索到动态规划的逐步推导
算法·深度优先·动态规划
学嵌入式的小杨同学4 小时前
【嵌入式 C 语言实战】交互式栈管理系统:从功能实现到用户交互全解析
c语言·开发语言·arm开发·数据结构·c++·算法·链表
多米Domi0114 小时前
0x3f 第40天 setnx的分布式锁和redission,写了一天项目书,光背了会儿八股,回溯(单词搜索)
数据结构·算法·leetcode
乐迪信息4 小时前
乐迪信息解决港口船型识别难题!AI算法盒子检测船舶类型
人工智能·算法·智能电视