-递归 Lecture 5 - Recursion

官方信息技术老师·14 页·深入(追求细节与边界)·0 次浏览·3 天前
递归调用栈复杂度边界

-递归 Lecture 5 - Recurs

搞懂递归调用、终止条件、复杂度与常见边界

按 空格/→ 演示下一步

1 / 14 页

全部页面点击任意一页,跳回舞台从这页播放

递归调用栈复杂度边界

-递归 Lecture 5 - Recurs

搞懂递归调用、终止条件、复杂度与常见边界

1第 1 页 · -递归 Lecture 5 - Recurs

Lecture 5 Introduction

你有没有遇到过:打开一个文件夹,里面还有一个,再打开还有一个……这就是「递归」——一个东西里包含着结构相同、规模更小的自己。本讲我们就把这种思维彻底拆开。

递归定义
函数在执行过程中直接或间接调用自身
两个必要条件
基例(最小问题的直接答案)+ 递推(把问题缩小一层)
自相似结构
问题或数据本身嵌套 / 分形,才适合用递归
典型应用
树与图遍历、分治、回溯、语法解析、动态规划
俄罗斯套娃对应 →递归调用

最里层无法再拆的 = 基例;每层都套着一个更小的同类 = 递推

2第 2 页 · Lecture 5 Introduction

Iterative Algorithms

上页我们讲了递归——函数调用自己、把问题拆成更小的同类子问题。但同样的事,用循环也能做,而且往往更省空间。

定义
用循环结构重复执行一组步骤,状态在循环体内显式更新
与递归等价
任何递归都能改写为迭代(显式栈+循环),反之亦然
空间优势
消除调用栈开销;尾递归可从 O(n) 降至 O(1)
转换手法
尾递归→直接循环;一般递归→引入显式栈模拟调用栈
俄罗斯套娃(外层套着内层)对应 →算盘珠子(同杆上反复拨动)

递归每次产生新实例嵌套(栈帧叠加),迭代在同一容器上反复更新状态(循环变量)

ak+1=f(ak), a0=ainita_{k+1} = f(a_k),\ a_0 = a_{\text{init}}
3第 3 页 · Iterative Algorithms

Recursive Algorithms

上一页迭代算法是用同一个动作循环往复地推进;递归走的是另一条路——把问题拆成规模更小的同类子问题,然后让函数在执行中调用自己。

自调用结构
函数在执行过程中调用自身,每次处理规模更小的同类子问题
终止条件(基底)
递归必须有一个最小、最简单的情形不再向下递归,否则永不结束
递归推进方向
每次调用必须向基底靠近,否则会陷入无限递归直至栈溢出
调用栈帧
每次调用压入新栈帧保存局部状态;深度过大易栈溢出
典型适用场景
树/图遍历、分治(快排归并)、回溯、DFS、阶乘与斐波那契
俄罗斯套娃对应 →递归算法

每层套娃内嵌更小的同形套娃,最里层是实心不可拆的——对应递归调用与终止条件

f(n)={1,n=0nf(n1),n>0f(n) = \begin{cases} 1, & n = 0 \\ n \cdot f(n-1), & n > 0 \end{cases}
4第 4 页 · Recursive Algorithms

Inductive Reasoning

递归算法会写了,可怎么证明它对所有输入都对?这时要回到它背后的数学逻辑:归纳推理。

归纳基础
验证命题对最小情形成立,是归纳的种子与起点
归纳假设
假设命题对任意 k 成立,是临时借的踏板而非已证结论
归纳步骤
由 k 成立推出 k+1 成立,是整个证明的核心动作
对应递归
递归出口 ↔ base case;递归调用 ↔ inductive step,结构一一镜像
爬楼梯对应 →归纳推理

第 1 级站稳 + 任意级都能迈上下一级 → 任意高度都能走到

5第 5 页 · Inductive Reasoning

Factorial

上一节归纳法告诉我们:验证 base case + 证明归纳步 = 全命题成立。阶乘的递归定义正是这个证明结构的『计算版』——把『推出 n+1』换成『调用 n 自己』。

0! = 1
递归唯一可停下来的点;0 的阶乘被定义为 1,是为了递推式在 n=1 时也能成立
n!=n·(n-1)!
把问题规模从 n 降到 n-1,调用自己一次再乘以 n
栈深度 = n
每层递归压一帧调用栈;n 过大时 C/Java 等语言可能直接栈溢出
应用:排列组合
P(n,k)、C(n,k) 等几乎所有计数公式都以阶乘为底座
俄罗斯套娃对应 →阶乘的递归展开

打开大娃,里面是个更小的自己;最里层那只空心小娃就是 0! = 1

n!={1n=0n×(n1)!n1n! = \begin{cases} 1 & n = 0 \\ n \times (n-1)! & n \geq 1 \end{cases}
6第 6 页 · Factorial

Towers of Hanoi

阶乘递归只调用自身一次。汉诺塔每次「自己调两次自己」——递归的真正威力,要靠这座塔才完整展露。

三柱问题
3 根柱子、n 个从小到大叠放的圆盘,每次只能移一片,大盘禁压小盘
三步拆解
把 n-1 片先挪到中转柱;移最底那片到目标;再把 n-1 片从中转挪到目标
递归终止
n=1 时无需中转,直接一片结束——这就是递归的 base case
最少步数
T(n)=2T(n-1)+1,递推解为 2ⁿ−1;n=64 时约 1.8×10¹⁹ 步
要搬最底下那只箱子对应 →汉诺塔的中间步骤

得先腾空它上面所有的箱子——中转柱就是临时仓库

T(n)=2T(n1)+1,T(1)=1    T(n)=2n1T(n) = 2\,T(n-1) + 1,\quad T(1)=1 \;\Longrightarrow\; T(n)=2^{n}-1
7第 7 页 · Towers of Hanoi

Fibonacci

阶乘只回头看一步:F(n)=n·F(n-1)。但 Fibonacci 要回头看两步:F(n)=F(n-1)+F(n-2)——一次调用分成两个子调用,节点数随之呈指数膨胀。

递归定义
F(n) = F(n-1) + F(n-2),n ≥ 2,后项等于前两项之和
基础情形
F(0) = 0,F(1) = 1,递归到此停
双分支结构
一次调用展开为两个子调用,与阶乘的单链不同
重叠子问题
同一 F(k) 被反复求解,这是动态规划的入口
典型应用
兔子繁殖、爬楼梯、黄金分割近似、算法分析母题
爬 n 级台阶对应 →Fibonacci 双分支

到第 n 阶可从前一阶跨 1 步,或从再前一阶跨 2 步

F(n)=F(n1)+F(n2),F(0)=0,F(1)=1F(n) = F(n-1) + F(n-2),\quad F(0)=0,\quad F(1)=1
8第 8 页 · Fibonacci

Demo: Fibonacci

上一页我们见识了 Fibonacci 数列——定义优雅,三行就能写成函数。但真正运行起来,朴素递归慢得不优雅。今天拆开调用树,看看代价藏在哪里。

朴素递归三行
F(n)=F(n-1)+F(n-2) 加两个 base case,写出来只需几行。
调用树指数级
F(n) 展开为二叉树,叶子数约等于 φⁿ,n 每加 1 节点几乎翻倍。
重叠子问题
F(3) 在 F(5) 的递归树里被算了 5 次——同一道题反复做。
记忆化修复
用一张表存算过的 F(k),下次直接读,复杂度从 O(φⁿ) 降到 O(n)。
做过的数学题不想重算对应 →记忆化递归

算过的 F(k) 写进小本子,下次直接抄——memo 本意就是「记住」

F(n)=F(n1)+F(n2),F(0)=0, F(1)=1F(n)=F(n-1)+F(n-2),\quad F(0)=0,\ F(1)=1
9第 9 页 · Demo: Fibonacci

Recursion on Strings

Fibonacci 用「两个更小的自己」算出答案。字符串更直接:每次削掉一个字符,剩下的还是字符串——天然就是递归的形状。

Base case: 空串
反复削字符必然会落到 ""——这是递归唯一会停下来的地方
结构分解
s = first(s) + rest(s);先对 first 做点事,再对 rest 做同样的事
处理顺序
头递归:先做事再下钻;尾递归:先下钻再做事,结果反向累积
典型题型
回文判断、字符串反转——同一结构,顺序不同导致思路不同
拆快递包装对应 →字符串递归

每层处理最外圈,剩余仍是同类结构,直到最里面的物品(base case)

10第 10 页 · Recursion on Strings

Demo: Palindromes

前页我们把递归用在字符串上,现在看一个最经典的递归字符串问题——回文判断。它的递归结构简直是为这个问题量身定做的。

基础情形
空串和单字符无需比较,天然就是回文
递归结构
首尾字符相同,且去掉首尾后的子串仍是回文
问题缩小
每次递归砍掉首尾两个字符,长度减少 2
边界陷阱
大小写、空格、Unicode、奇偶长度差异都要小心
两人从两端对向行走比对对应 →回文判断的递归过程

每步各看一字符,外层匹配才能进入内层;走到中间相遇即完成

P(s)={trueif s1(s0=sn1)P(s1n2)otherwiseP(s) = \begin{cases} \text{true} & \text{if } |s| \le 1 \\ (s_0 = s_{n-1}) \land P(s_{1\ldots n-2}) & \text{otherwise} \end{cases}
11第 11 页 · Demo: Palindromes

Global Variables

Global Variables:定义、要点与典型应用

Global Variables
Global Variables:定义、要点与典型应用
12第 12 页 · Global Variables

本节要点

  • 递归三件套:base case、缩小规模、信任子问题的解
  • 递归 = 数学归纳法的程序化对应
  • 代价不只在调用栈:朴素斐波那契是O(2ⁿ)
  • 递归中改全局变量=跨层共享状态,要管好出口
  • 递归能想到,迭代就能做到;选哪种看问题结构
延伸主题:递归的复杂度分析记忆化与动态规划尾递归优化
13第 13 页 · 本节要点

课后思考

先独立思考,再对照参考答案。三问分别覆盖核心回顾、应用与边界迁移。

1递归与数学归纳法为什么结构上同构?这种对应能帮你写出正确的递归吗?

参考答案base case 对应归纳基础,递归步骤对应归纳假设——先验证最小情形成立,再假设更小一层成立推出当前成立。

2能否用递归实现字符串反转?请描述关键思路,不需要写完整代码。

参考答案reverse(s) = reverse(s[1:]) + s[0]。先递归到最深层(空串或单字符为 base case),逐层回溯时把首字符拼到尾部。

3为什么直接递归计算 fib(50) 几乎不可用?瓶颈在哪里?

参考答案fib(n−1) 与 fib(n−2) 会各自重复计算 fib(n−3),子问题指数级重叠 → 时间复杂度 O(2ⁿ)。需记忆化或改迭代。

14第 14 页 · 课后思考