当前位置: 首页 > news >正文

第9讲:函数递归——用“套娃”思维解决复杂问题

🔁 第9讲:函数递归——用“套娃”思维解决复杂问题 🧩

适合刚掌握函数基础的你,学会递归,解锁编程新维度!


📚 目录

  1. 什么是递归?📦
  2. 递归的两大限制条件 🔒
  3. 递归实战案例 💡
    • 3.1 求 n 的阶乘
    • 3.2 顺序打印整数每一位
  4. 递归 vs 迭代:效率之争 ⚔️
  5. 斐波那契数列的“陷阱” ⚠️
  6. 递归的适用场景 🎯
  7. 拓展挑战:经典递归问题 🚀

什么是递归?📦——函数调用自身的“套娃”艺术

“递归”不是魔法,而是一种将大问题拆解为小问题的思维方式。

🧠 核心定义

递归 = 函数自己调用自己

#include <stdio.h>
int main() {printf("hehe\n");main(); // ❌ 错误示范:无限递归return 0;
}

📌 这段代码会无限打印 hehe,最终导致 栈溢出(Stack Overflow)


🔁 递归的哲学:大事化小

递归的精髓在于:

把一个复杂问题,转化为一个与原问题相似但规模更小的子问题来解决。

  • “递”:层层推进,拆解问题(递推)
  • “归”:子问题解决后,逐层返回结果(回归)

🎯 就像剥洋葱,一层一层剥到核心,再一层一层合回去。


递归的两大限制条件 🔒——防止“无限套娃”

写递归函数,必须满足两个铁律,否则就是“死递归”!

条件说明示例(阶乘)
✅ 1. 存在限制条件当满足某个条件时,递归必须停止n == 0 时返回 1
✅ 2. 趋近于限制条件每次递归调用,都要更接近终止条件Fact(n-1) → 规模减小

📌 没有终止条件 = 无限递归 → 栈溢出崩溃!


递归实战案例 💡

案例1:求 n 的阶乘 🎯

🧮 数学定义
n! = n × (n-1) × (n-2) × ... × 1
0! = 1 (特殊规定)
🔁 递归关系
Fact(n) = n × Fact(n-1)
✅ 代码实现
int Fact(int n) {if (n == 0) {           // ✅ 终止条件return 1;} else {return n * Fact(n-1); // ✅ 规模减小}
}int main() {int n;scanf("%d", &n);printf("%d! = %d\n", n, Fact(n));return 0;
}
🖼️ 递归过程图解(以 Fact(5) 为例)
Fact(5)
├── 5 * Fact(4)├── 4 * Fact(3)├── 3 * Fact(2)├── 2 * Fact(1)├── 1 * Fact(0)112624120

案例2:顺序打印整数每一位 🔢

🎯 题目

输入 1234,输出 1 2 3 4

🧠 思路分析

直接取最高位很难?那就先打印前面的位,再打印最后一位

Print(1234)
├── Print(123)  → 先打印前三位
└── printf("4") → 再打印最后一位
✅ 代码实现
void Print(int n) {if (n > 9) {                    // ✅ 终止条件:不是一位数Print(n / 10);              // ✅ 规模减小:去掉最后一位}printf("%d ", n % 10);          // 打印最后一位(递归返回时执行)
}int main() {int m;scanf("%d", &m);Print(m);return 0;
}
🖼️ 执行流程(Print(1234)
Print(1234)
├── Print(123)├── Print(12)├── Print(1) → n<=9,不递归← printf("1 ")printf("2 ")printf("3 ")printf("4 ")
输出:1 2 3 4

📌 关键点printf 在递归调用之后,所以是“归”的过程中打印,自然就是从左到右


递归 vs 迭代:效率之争 ⚔️

特性递归(Recursion)迭代(Iteration)
代码风格简洁,接近数学定义稍长,逻辑清晰
空间效率低(每次调用占用栈帧)高(通常 O(1) 空间)
时间效率可能低(重复计算)通常更高
适用场景结构天然递归(树、图)简单循环任务

🔄 阶乘的迭代实现(更高效)

int Fact(int n) {int ret = 1;for (int i = 1; i <= n; i++) {ret *= i;}return ret;
}

优点

  • 无栈溢出风险

  • 时间复杂度 O(n),空间复杂度 O(1)

    🔥 递归的性能代价:栈帧开销与栈溢出风险 ⚠️

    Fact函数虽然能正确计算结果,但其递归调用过程伴随着显著的运行时开销。

    🧱 栈帧(Stack Frame)机制

    在C语言中,每次函数调用都会在内存的栈区(stack)申请一块空间,称为函数栈帧运行时堆栈

    • 作用:保存该次调用的局部变量、参数、返回地址等信息。
    • 生命周期:函数不返回,栈帧就一直占用内存。

    📦 递归的栈空间消耗

    当递归发生时:

    1. 每一次递归调用都会创建独立的栈帧
    2. 这些栈帧会层层叠加,直到递归终止。
    3. 然后才从最内层开始,逐层释放栈帧(“回归”过程)。
    栈顶 ┌────────────┐│ Fact(0)    │ ← 返回,释放├────────────┤│ Fact(1)    │ ← 返回,释放├────────────┤│ Fact(2)    │ ← 返回,释放├────────────┤│   ...      │├────────────┤│ Fact(n-1)  │ ← 等待 Fact(n-2) 返回├────────────┤│ Fact(n)    │ ← 最外层调用
    栈底 └────────────┘
    

    🚨 栈溢出(Stack Overflow)风险

    • 如果递归层次过深,会创建大量栈帧。
    • 栈区空间有限(通常几MB),当栈帧总大小超过栈空间时,程序就会崩溃,报错 Stack overflow
    • 因此,递归虽然简洁,但不适合深度过大的问题

    📌 结论:递归的简洁性是以时间和空间开销为代价的,需谨慎使用。


斐波那契数列的“陷阱” ⚠️——递归的反面教材

🧮 斐波那契定义

F(1) = 1
F(2) = 1
F(n) = F(n-1) + F(n-2)  (n > 2)

❌ 低效的递归实现

int Fib(int n) {if (n <= 2) return 1;return Fib(n-1) + Fib(n-2);
}
📉 问题:指数级重复计算!

Fib(5) 为例:

        Fib(5)/        \Fib(4)        Fib(3)/    \        /    \
Fib(3) Fib(2) Fib(2) Fib(1)
/    \
Fib(2) Fib(1)

📌 Fib(3) 被计算了 2次Fib(2) 被计算了 3次

n=50 时,计算时间长得无法接受!


✅ 高效的迭代实现

int Fib(int n) {if (n <= 2) return 1;int a = 1, b = 1, c;for (int i = 3; i <= n; i++) {c = a + b;a = b;b = c;}return c;
}

优点

  • 时间复杂度:O(n)

  • 空间复杂度:O(1)

  • 无重复计算

    🔥 递归的性能代价:栈帧开销与栈溢出风险 ⚠️

    Fact函数虽然能正确计算结果,但其递归调用过程伴随着显著的运行时开销。

    🧱 栈帧(Stack Frame)机制

    在C语言中,每次函数调用都会在内存的栈区(stack)申请一块空间,称为函数栈帧运行时堆栈

    • 作用:保存该次调用的局部变量、参数、返回地址等信息。
    • 生命周期:函数不返回,栈帧就一直占用内存。

    📦 递归的栈空间消耗

    当递归发生时:

    1. 每一次递归调用都会创建独立的栈帧
    2. 这些栈帧会层层叠加,直到递归终止。
    3. 然后才从最内层开始,逐层释放栈帧(“回归”过程)。
    栈顶 ┌────────────┐│ Fact(0)    │ ← 返回,释放├────────────┤│ Fact(1)    │ ← 返回,释放├────────────┤│ Fact(2)    │ ← 返回,释放├────────────┤│   ...      │├────────────┤│ Fact(n-1)  │ ← 等待 Fact(n-2) 返回├────────────┤│ Fact(n)    │ ← 最外层调用
    栈底 └────────────┘
    

    🚨 栈溢出(Stack Overflow)风险

    • 如果递归层次过深,会创建大量栈帧。
    • 栈区空间有限(通常几MB),当栈帧总大小超过栈空间时,程序就会崩溃,报错 Stack overflow
    • 因此,递归虽然简洁,但不适合深度过大的问题

    📌 结论:递归的简洁性是以时间和空间开销为代价的,需谨慎使用。


递归的适用场景 🎯——何时使用递归?

推荐使用递归的场景

问题类型说明
🌲 树形结构遍历如文件夹遍历、二叉树遍历
🧩 分治算法快速排序、归并排序
🧱 天然递归结构汉诺塔、斐波那契(教学)
🔍 回溯算法八皇后、迷宫求解

避免使用递归的场景

  • 简单循环可解决的问题(如阶乘、斐波那契)
  • 深度过大的递归(栈溢出风险)

拓展挑战:经典递归问题 🚀

🐸 问题1:青蛙跳台阶

一只青蛙一次可以跳1级或2级台阶,问跳上n级台阶有多少种跳法?

📌 递归关系f(n) = f(n-1) + f(n-2)(斐波那契!)

🏰 问题2:汉诺塔(Hanoi Tower)

三根柱子,A柱上有n个盘子(上小下大),要求借助B柱,将所有盘子移动到C柱,且大盘不能压小盘。

📌 递归思路

  1. 把上面 n-1 个盘子从 A → B(借助 C)
  2. 把第 n 个盘子从 A → C
  3. n-1 个盘子从 B → C(借助 A)
void hanoi(int n, char A, char B, char C) {if (n == 1) {printf("%c -> %c\n", A, C);} else {hanoi(n-1, A, C, B); // A→B 借助 Cprintf("%c -> %c\n", A, C);hanoi(n-1, B, A, C); // B→C 借助 A}
}

✅ 学习收获总结

技能掌握情况
✅ 理解递归本质:函数自调用✔️
✅ 掌握两大限制条件✔️
✅ 实现阶乘、打印数字等递归✔️
✅ 理解递归 vs 迭代的优劣✔️
✅ 识别递归的适用场景✔️
✅ 避免低效递归(如Fib)✔️

🎯 递归不是万能钥匙,但它是打开复杂世界的一扇门
你已经掌握了“大事化小”的编程思维,继续加油,下一个难题等你来攻克!💪🔥

💬 需要本讲的 递归动画演示 / 汉诺塔可视化 / 源码工程?欢迎继续提问,我可以一键打包给你!

http://www.dtcms.com/a/453297.html

相关文章:

  • 东莞网站竞价推广运营百度云虚拟主机如何建设网站
  • 权限管理混乱微服务安全架构:OAuth2.0+JWT无感刷新方案非法请求拦截率
  • 北京理工大学网站开发与应用彩票网站开发彩票网站搭建
  • 网站建设公司重庆装修设计公司公司价格表
  • 厦门市建设局查询保障摇号网站首页系统开发板价格
  • 金溪网站建设制作电商系统开发公司
  • 直方图 vs 箱线图:两种看数据分布的思路差异
  • 构建AI智能体:五十六、从链到图:LangGraph解析--构建智能AI工作流的艺术工具
  • 【Spring】AOP的核心原理配方
  • 惠州建站平台建筑人才网招聘信息
  • 《Cargo 参考手册》第一章:清单
  • MVCC 多版本并发控制
  • 【AI智能体】Coze 打造AI数字人视频生成智能体实战详解:多模态情感计算与云边协同架构
  • 重庆网站建设培训机构学费重庆市官方网站
  • 关系建设的网站上海网站seo招聘
  • Vue router-view和router-link分开写在不同的组件中实现导航栏切换界面
  • Wan2.2-Animate V2版 - 一键替换视频角色,实现角色动作及表情同步迁移替换 支持50系显卡 ComfyUI工作流 一键整合包下载
  • Coordinate Attention for Efficient Mobile Network Design 学习笔记
  • 初识MYSQL —— 数据类型
  • 大型网站建设行情南通专业网站设计制作
  • 【AI智能体】Coze 打造AI数字人视频生成智能体实战详解:从0到1构建可交互虚拟主播
  • LabVIEW使用3D场景光照
  • 河北建设厅网站修改密码在哪wordpress 前台 很慢
  • 数字设计 综合工具 yosys 源码安装与应用简介
  • HikariCP 连接池完全指南
  • 绵竹网站建设大连装修公司
  • C++空值初始化利器:empty.h使用指南
  • 电子版康奈尔笔记写作方案对比
  • (3)SwiftUI 的状态之上:数据流与架构(MVVM in SwiftUI)
  • 郴州网站seo个人兴趣网站设计