递推与递归笔记

一、核心概念梳理

(一)递推算法

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

(二)递归算法

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

二、典型案例解析

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

  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
    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++ 数组循环,效率更高),复杂拆解类问题用递归(代码更简洁)。
相关推荐
Forever Nore14 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考16 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX17 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面17 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号17 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽18 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI18 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者19 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者19 小时前
【征程6】校准量化中HistogramObserver解析
算法
OuO-220 小时前
笔试强训 Day 34:ISBN 号码、kotori 和迷宫、矩阵最长递增路径
java·算法·矩阵