[算法系列] 深入递归本质+经典例题解析——如何逐步生成, 以此类推,步步为营
本文是递归系列的第三篇, 第一篇介绍了递归的形式以及递归设计方法(迭代改递归),;第二篇以递归为引子, 详细介绍了快排和归排以及堆排的核心思想; 本篇主要通过几个题, 从递推, 归纳法的角度, 深入了介绍了递归的本质和具体应用.
往期回顾:
回顾我们在第一篇文章讨论的递归中, 下面是我们能够看到现象形式:
f(n) -> f(n - 1) -> f(n-2) -> ... -> f(1)
但实际本质是: 为了解决/完成 f(n), 必先完成f(n- 1); 为解决f(n-1),必先解决f(n-2) … 那么最先要解决f(1)
f(1) -> f(2) -> f(3) -> ... ->f(n)
回顾以前学过的数学归纳法:
1. 证明当k=1时,条件成立
2. 假设k=n(n>=1)时,条件成立
3. 证明k=n+1,条件成立
得到结论: k取任意正整数时,条件成立
如果没记错的话这叫第一数学归纳法, 往往我们用来证明构造的某些式子在给定自然数集合(全体或局部)的正确性. 而数学归纳法本质是什么呢? 通俗来看, 就是首先证明了k=1时的正确性, 然后证明k = n 成立可以推导出k=n+1成立. 根据上述两个条件可以得出k=2也就成立了… 然后k=3也就成立… 本质是递推.
- 递归解决的本质是先从f(1)->f(2)->…->f(n), 小问题解决了,再解决大问题
- 数学归纳法式从k = 1 逐层证明, 或者说证明k=n和k=n+1的关系,然后递推
- 递推, 就是按照前一个(或几个)的关系推理出下一个 …
recursion一词既可以翻译为递推,也可以翻译为递归, 这里的归应该是是规约的意思. 注意这里的递归和编程形式中的 递归调用 是有点区别的, 编程中谈到的形式化更多一些, 而数学本质还是和递归递推没有区别.
递归, 递推, 数学归纳法本质正是同一种东西.
好了,现在看来知道了这些似乎作用不大. 我们还是举个例子, 搞懂递归, 看这篇就够了 !! 递归设计思路 + 经典例题层层递进 中的青蛙上楼梯问题.
1. 再谈青蛙上楼梯
楼梯有n个台阶, 一个青蛙一次可以上1 , 2 或3 阶 , 实现一个方法, 计算该青蛙有多少种上完楼梯的方法
文中给出了递归的解法:
(回忆找重复,找变化,找出口)
假如青蛙上10 阶, 那么其实相当于要么 站在第9 阶向上走1步,要么 站在第8 阶向上走两步, 要么在第7阶向上走3步. 每个大于3阶楼梯的问题都可以看成几个子问题的堆叠
变化:令f(n) 为 青蛙上n阶的方法数. 则f(n) = f(n -1) +f(n - 2) + f(n -3) , 当n >= 3
出口: 当n = 0 时 ,青蛙不动 , f(0) = 0; n = 1时 ,有1种方法 , n = 2 时 有2 种方法
def f(n):
if n == 0 :
return 1 #站着不动也得返回1的, 因为实际上0种方法的是没意义
if n == 1:
return 1
if n == 2:
return 2
return f(n - 1) +f(n - 2) +f(n -3)
显然这样的递归方法不是很直观的, 其实一开始拿到这题 , 普通地想, 应该是拿出张白纸来, 左边起名一列: 阶数 , 右边起名一列: 走法
阶数 走法
1 1 0->1
2 2 0->1->2 0->2
3 4 0->1->2->3 0->1->3 0->2->3 0->3
4 7 ...
... ...
详细康康阶数为4时的走法:
注意我分成了三列写, 如果不看红色部分的话, 三列分别代表了上第1,2,3阶的方法. 现在带着红色的 ->4 一起看:
- 第一列: 相当于先上到第1阶再一次上到4 (因为最大可以跨3阶嘛)
- 第二列: 相当于先上到第2阶再一次上到4 (相当于最后一次跨2阶嘛)
- 第三列:相当于先上到第3阶再一次上到4(最后一次跨1阶即可)
显然上到第四阶的方法刚好就是这三列的和了 …
到这里, 有兴趣的同学可以在写出阶数为5的走法. 但其实也会得到下面的结论:
- 第一列: 相当于先上到第2阶再一次上到5 (因为最大可以跨3阶嘛)
- 第二列: 相当于先上到第3阶再一次上到5 (相当于最后一次跨2阶嘛)
- 第三列:相当于先上到第4阶再一次上到5(最后一次跨1阶即可)
显然上到第五阶的方法刚好就是这三列的和了 …
…想一想, 规律也就可以得出了
阶数为n的走法. 但其实也会得到下面的结论:
- 或者先上到第n-3阶再一次上到n (因为最大可以跨3阶嘛)
- 或者先上到第n-2阶再一次上到n (相当于最后一次跨2阶嘛)
- 或者于先上到第n-1阶再一次上到n(最后一次跨1阶即可)
所以 f(n) = f(n -1) +f(n - 2) + f(n -3) 不是凭空产生, 而真是一步一步的像上面一样推出来 – 递归表达也是如此
下面就可以自然的得到递归法实现了
def go_stairs(n):
if n <= 1:
return 1
if n == 2 :
return 2
if n == 3 :
return 4
return go_stairs(n - 1) + go_stairs(n - 2) + go_stairs(n - 3)
写出了出口条件, 写出了递推式, 计算机不就帮我们像上面一样, 一步一步地推下去了么…
同样的, 我们也可以按照我们的推理演算的顺序, 用一个长度为3的数组, 保存每次得到的f(n -1) ,f(n - 2) ,f(n -3), 下一轮再更新…这就是我们递推的迭代法实现 :
def go_stairs_ite(n):
#声明一个长度为4的数组保存每次计算得到值, 用于存储每次计算所需的三个值和一个结果值
arr =[]
if n <= 1:
return 1
if n == 2 :
return 2
if n == 3 :
return