递推与递归笔记

一、核心概念梳理

(一)递推算法

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

(二)递归算法

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

二、典型案例解析

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

  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++ 数组循环,效率更高),复杂拆解类问题用递归(代码更简洁)。
相关推荐
renhongxia15 分钟前
如何基于知识图谱进行故障原因、事故原因推理,需要用到哪些算法
人工智能·深度学习·算法·机器学习·自然语言处理·transformer·知识图谱
坚持就完事了5 分钟前
数据结构之树(Java实现)
java·算法
算法备案代理8 分钟前
大模型备案与算法备案,企业该如何选择?
人工智能·算法·大模型·算法备案
赛姐在努力.32 分钟前
【拓扑排序】-- 算法原理讲解,及实现拓扑排序,附赠热门例题
java·算法·图论
野犬寒鸦2 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
霖霖总总2 小时前
[小技巧66]当自增主键耗尽:MySQL 主键溢出问题深度解析与雪花算法替代方案
mysql·算法
rainbow68892 小时前
深入解析C++STL:map与set底层奥秘
java·数据结构·算法
wangjialelele2 小时前
平衡二叉搜索树:AVL树和红黑树
java·c语言·开发语言·数据结构·c++·算法·深度优先
驱动探索者3 小时前
linux mailbox 学习
linux·学习·算法
ringking1233 小时前
autoware-1:安装环境cuda/cudnn/tensorRT库函数的判断
人工智能·算法·机器学习