(e)迭代与递归
看懂递归的展开与回溯,对比迭代的差异与权衡
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
(e)迭代与递归
看懂递归的展开与回溯,对比迭代的差异与权衡
: 迭代与递归
数一摞书:拿一本数 1,再拿一本数 2,一本一本地数;或者把问题变成"最上面一本 + 剩下那摞有多少本",对剩下那摞重复同一动作,直到剩 0 本。前者是迭代,后者就是递归。
每打开一层看到更小的一层,直到最小那颗为止——对应递归不断调用更小的自己
: 减而治之
递归能把任何问题变成「对自身的调用」,但具体怎么「变小」决定了策略。减而治之是最朴素的一条:从规模 n 退回 n-1,把子问题做完,再补回当前这步——里面藏着递归设计的整套思维。
每打开一层,剩下那层同型但更小;直到最里那颗(基例),再把外壳逐个合回去(回归)
: 递归跟踪
从「减而治之」我们把问题递归变小求解,但递归一旦深起来,脑子里就乱成一团——怎么才能看清它在做什么?这就要靠递归跟踪。
每层套娃是一次调用,逐层拆开记录编号,直到最小的不能拆的
: 递推方程
递归跟踪让我们看清了每一次调用的开销,但调用树一旦指数级膨胀,手工追踪就不再现实。我们需要一个数学工具,把整棵调用结构压缩成一行——这就是递推方程。
到第 n 级的走法 = 到 n-1 级的走法 + 到 n-2 级的走法,每级都用更小的级表达自己
: 数组倒置
: 数组倒置:定义、要点与典型应用
: 分而治之
上一节的减而治之,每步只把问题缩小一点点。但有些问题更顺手——一刀切成几块,各算各的,最后拼起来。这就是分而治之。
按零件分包 → 各人独立拼 → 模块合装成整体
: 二分递归:数组求和
上页刚拆完分而治之的'分-解-合'骨架。这页拿最经典的例子——二分递归求数组和——把它填满。注意:每个层级同时发出两个递归调用,正是'分'字最直观的体现。
工头把一摞账本对半分给两个副手,副手再对半分,直到没人只能自己算,再一层层把数字加回来报给工头
二分递归:Max2
求数组最大值,最直白的做法是从头扫到尾记一个当前最大——这是迭代。Max2 则把数组对半劈开,各自递归求最大,再比一次。思路简单,但时空代价和迭代版本差别显著。
每场比赛从两人中留下强者;递归树每层把两组的冠军再比一次,决出全局最大
: Max2:二分递归
上一节的数组求和,把两半递归结果直接相加;Max2 也是这个套路,但'合'起来不再是加法,而是两场比较。同一个二分递归框架,换了一种归并动作。
两分区各出冠军,决出总冠军后,再从败者中选最强者为亚军
本节要点
- ✓递归本质是问题规模的自我引用,复杂度取决于递推关系而非写法
- ✓减而治之:每层减一个常数项,时间通常呈 O(n)
- ✓分而治之:规模折半加合并,T(n)=aT(n/b)+f(n) 是骨架
- ✓递归跟踪表把调用-返回翻译成实例树,是分析时空代价的利器
- ✓同一问题可有多种递归分解,需用复杂度下界判定最优
课后思考
先自己想,再点开参考答案对照——每个问题都值得琢磨几分钟。
参考答案从图灵等价看,循环对应调用栈的反复进出。控制结构变了,但数据依赖没变。代价是栈空间开销,以及某些情形下编译器能否做尾递归优化。
参考答案能照搬——把head.next指向prev,再递归处理剩余段。关键是先保存next再改指向,否则递归丢失入口。减而治之的思路没变,只是剩余部分的获取从下标变成了指针。
参考答案方程改写为T(n)=T(⌊n/2⌋)+T(⌈n/2⌉)+1,主定理仍是O(n)。下界成立依赖“必须比较n-1次”这一基本事实,递归只是常数因子的优化。