Lecture 11:栈在递归中的应用
408 学习笔记:Lecture 11:栈在递归中的应用。保留原有知识内容、公式与图示;有对应原件时提供查看和下载入口。
本页目录
11.1 基本概念
定义:#递归
描述: 递归是指一个函数在求解问题时,直接或间接调用自身,把原始问题转化为属性相同但规模更小的子问题。
适用场景
- 原始问题可以分解为规模较小的同类问题。
- 子问题与原问题具有相同性质。
- 问题存在明确的递归出口,也就是边界条件。
递归必须包含
- 递归表达式:把大问题转化为小问题。
- 边界条件:让递归在某个位置停止。
11.2 递归示例
11.2.1 阶乘
定义:#阶乘递归
描述: 计算正整数阶乘时,$n!$ 可以转化为 $n\times(n-1)!$,直到遇到边界条件。
阶乘的递归定义: $$ factorial(n)= \begin{cases} n\times factorial(n-1), & n>1 \ 1, & n=1 \ 1, & n=0 \end{cases} $$
解释
- 递归表达式: $$ factorial(n)=n\times factorial(n-1) $$
- 边界条件: $$ factorial(1)=1 $$
$$ factorial(0)=1 $$
11.2.2 斐波那契数列
定义:#斐波那契递归
描述: 斐波那契数列中,当前项可以由前两项相加得到。
斐波那契数列的递归定义: $$ Fib(n)= \begin{cases} Fib(n-1)+Fib(n-2), & n>1 \ 1, & n=1 \ 0, & n=0 \end{cases} $$
解释
- 递归表达式: $$ Fib(n)=Fib(n-1)+Fib(n-2) $$
- 边界条件: $$ Fib(1)=1 $$
$$ Fib(0)=0 $$
原图的阶乘、斐波那契递归式及边界条件均已完整改写为上方 LaTeX 公式,没有额外的空间结构信息,故不重复嵌图。
11.3 栈和递归的关系
11.3.1 函数调用栈
定义:#递归工作栈
描述: 递归调用时,系统会用函数调用栈保存每一层函数调用的信息,这个函数调用栈也称为递归工作栈。
递归调用时需要保存的信息
- 当前函数的参数。
- 当前函数的局部变量。
- 函数返回地址。
- 当前调用层还没有执行完的上下文。
栈的作用
- 每调用一次递归函数,就会把当前调用的信息压入栈。
- 每结束一层递归调用,就会从栈中弹出该层调用的信息。
- 最后被调用的函数最先执行结束,符合栈的后进先出特点: $$ \text{LIFO}=\text{Last In First Out} $$
11.3.2 为什么递归天然依赖栈
以递归调用为例:
Fib(4)
-> Fib(3)
-> Fib(2)
-> Fib(1)
调用时不断压栈:
Fib(4), Fib(3), Fib(2), Fib(1)
返回时从最后调用的函数开始:
Fib(1) -> Fib(2) -> Fib(3) -> Fib(4)
这正好是: $$ \text{后调用,先返回} $$
11.4 递归实现
11.4.1 斐波那契数列代码
以下教学示例约定输入为非负整数 $n$;负数不满足递归出口条件,不能直接传入。
代码
int Fib(int n) {
if (n == 0)
return 0;
else if (n == 1)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
对应关系
| 代码 | 含义 |
|---|---|
if (n == 0) return 0; |
边界条件 |
else if (n == 1) return 1; |
边界条件 |
return Fib(n - 1) + Fib(n - 2); |
递归表达式 |
PDF 代码修正
- 原 PDF 的代码中
int Fib(it n)应理解为:
int Fib(int n)
11.4.2 递归代码的书写要点
- 先写边界条件。
- 再写递归表达式。
- 每次递归调用必须让问题规模变小。
- 必须保证递归能够到达边界条件。
错误写法示例
int f(int n) {
return f(n);
}
问题
- 问题规模没有变小。
- 没有边界条件。
- 会无限递归,最终可能栈溢出。
11.5 递归的缺点
11.5.1 栈溢出
定义:#栈溢出
描述: 栈溢出是指函数调用层数过多,递归工作栈无法继续容纳新的调用信息。
原因
- 递归层数太深。
- 缺少边界条件。
- 边界条件写错,递归无法停止。
- 每层递归保存的信息太多。
后果
- 程序异常终止。
- 空间复杂度升高。
- 实际运行效率降低。
11.5.2 空间复杂度
- 递归算法除了数据本身,还需要额外的递归工作栈空间。
- 若递归深度为 $n$,每层调用占用常数空间,则空间复杂度通常为: $$ O(n) $$
阶乘递归
- 递归深度约为 $n$。
- 空间复杂度: $$ O(n) $$
普通斐波那契递归
- 单条调用链最大深度约为 $n$。
- 空间复杂度: $$ O(n) $$
- 但它会重复计算大量子问题,时间复杂度较高。
11.6 题型整理
题型:#递归与栈
第一部分:解题方法
- 看到递归,要想到函数调用栈。
- 每一次递归调用都会压栈。
- 每一次函数返回都会出栈。
- 最后被调用的函数最先结束,体现 LIFO。
- 递归层数越深,递归工作栈占用空间越多。
第二部分:典型例题
例题 1:递归为什么会用到栈?
答:
- 因为递归调用需要保存每一层函数的参数、局部变量和返回位置。
- 函数调用遵循后调用先返回。
- 这与栈的后进先出性质一致。
- 所以系统用函数调用栈管理递归过程。
例题 2:递归可能带来什么问题?
答:
- 递归层数过多时,可能导致栈溢出。
- 递归工作栈会增加额外空间开销。
- 若存在重复子问题,还可能导致时间开销很大。
第三部分:知识点复盘
- 递归不是新的数据结构,但递归执行过程依赖栈。
- 递归必须有边界条件。
- 递归必须让问题规模不断缩小。
- 函数调用栈也可以称为递归工作栈。
相关知识
- 科目导航:数据结构导航
- 阅读顺序:上一篇:栈在表达式求值中的应用 · 下一篇:特殊矩阵与压缩存储
- 关联专题:栈与队列代码
11.7 本节复盘
必须掌握
- 递归适合解决可以分解成同类小规模子问题的问题。
- 递归包含递归表达式和边界条件。
- 递归调用使用函数调用栈。
- 函数调用栈也称递归工作栈。
- 最后被调用的函数最先执行结束,符合 LIFO。
- 递归过深可能导致栈溢出。
易错点
- 递归不等于循环。
- 没有边界条件的递归会无限调用。
- 边界条件写错也可能导致栈溢出。
- 递归算法的空间复杂度要考虑递归工作栈。
- 斐波那契普通递归容易产生大量重复计算。
一句话总结
- 递归把大问题变成同类小问题,系统用递归工作栈保存每层调用,所以递归过程本质上体现了栈的后进先出。