本篇通过青蛙跳台阶、兔子数列(斐波那契数列)问题进一步理解递归思想的魅力。
上一篇最后的题目如下:
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级的台阶总共有多少种跳法?
归纳推理方式
不妨假设跳上n级台阶的跳法总数是 f(n),我们无法直接得知f(n)和n的关系,也即无法直接给出函数f(n)的表达式。
当我们在求解一个未知问题时,如果这个问题的通用解无法直接给出,但是个别情况下的解是可以给出的话,习惯上,我们会观察个别情况下的解,看是否可以推理出通用解。这种思维方式叫归纳推理法。
归纳推理是我们认识世界的基础,也是我们最习惯的思维方式。
所以,我们从f(1)开始归纳推理
f(1) = 1 [1]
f(2) = 2 [11,2]
f(3) = 3 [111,12,21]
f(4) = 5 [1111,112,121,211,22]
f(5) = 8 [11111,1112,1121,1211,2111,122,212,221]
f(6) = 13
……
其中[ ] 内表示不同的跳法,比如f(3)有3种跳法,分别是
111:连跳3次1级台阶
12:先跳1次1级台阶,再跳1次2级台阶
21:先跳1次2级台阶,再跳1次1级台阶
尽管我们在列举的时候,可以使用一个小技巧。
比如列举 f(4) 的所有可能时
先列举不跳2级台阶的可能,只有1种:1111
再列举跳了1次2级台阶的可能,有3种:112,121,211
再列举跳了2次2级台阶的可能,有1种:22
但随着n的增大,列举起来还是挺麻烦的。
不过好在如果足够细心的话,从f(1)到f(6),已经可以开始归纳推理了,我们可以归纳推理出一个猜想:
f(n) = f(n-1) + f(n-2)
这个猜想对f(3),f(4),f(5),f(6)都是成立的
f(3) = f(2) + f(1) = 2+1 = 3
f(4) = f(3) + f(2) = 3+2 = 5
f(5) = f(4) + f(3) = 5+3 = 8
f(6) = f(5) + f(4) = 8+5 = 13
假设这个猜想是正确的,我们就可以用Python循环求任意f(n)了。
程序如下,稍微注意一下在循环里3个变量的使用,如果不是特别清楚的话,可以回顾一下《10.裴蜀定理和欧几里得算法快速求解倒水问题》,里面有介绍。
def frog(n):
a = 1
b = 2
temp = 0
for i in range(3,n+1):
temp = a + b
a = b
b = temp
return temp
print(frog(7))
递归方式
再来回顾一下递归的本质,它是一种思想,一种解决问题的思维方式:
将规模大的问题转化为规模小的相似子问题来解决。
那么对于问题 f(n),该青蛙跳上n级台阶总共有多少种跳法,是否也能能转化为规模更小的子问题来解决呢?
我们换个思路,假设组队玩王者,使用的决胜策略是等着英雄最后放一把必杀技。
假设有 x 种玩法等着关羽放必杀技获胜,有y种玩法等着诸葛亮放必杀技获胜,请问一共有多种获胜的玩法。很显然是 x+y
对于问题f(n)也一样,不管前面怎么跳。
最后一步,可以从 n-1级台阶跳1级台阶到 n,也可以从 n-2级台阶跳2级台阶到n。
因此可得,f(n) = f(n-1) + f(n-2)。
这就把规模大的问题,转化成了规模更小的相似问题了。问题是什么。
问题f(n) 是该青蛙跳上n级台阶总共有多少种跳法。
规模大的问题和小的问题之间的关系。
f(n) = f(n-1) + f(n-2)“递”的结束条件。
n == 1 时,f(n) = 1;n==2时, f(n) == 2
在“递”、“归”的过程中做什么。
在“递”的过程中,也就是从 n --> 1的过程,什么都不要做。
在“归”的过程中,也就是从 1-->n 的过程,进行计算。
也即程序中的 return f(n-1) + f(n-2)
def frog(n):
if n == 1 :
return 1
elif n == 2:
return 2
else:
return frog(n-1)+frog(n-2)
print(frog(7))
巩固练习:更著名的兔子序列
意大利中世纪数学家斐波那契以兔子繁殖为例子引入了一个数列,称为“兔子数列”。
假设兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔子都不死,那么一年以后可以繁殖多少对兔子?
可以先思考,再阅读下面的内容
可以先用归纳推理的方法:
f(1) = 1,[A],才出生的兔子A
f(2) = 1,[A],成熟的兔子A
f(3) = 2,[A,Aa],老兔子A和它的宝宝Aa
f(4) = 3,[A,Aa,Ab],老兔子A和成熟的宝宝Aa,才出生的宝宝Ab
f(5) = 5,[A,Aa,Ab,Ac,Aa1],老兔子A和它的三对宝宝Aa,Ab,Ac,其中最大的宝宝Aa也生了一对宝宝 Aa1
f(6) = 8 [A,Aa,Ab,Ac,Ad,Aa1,Aa2,Ab1] ……
……
我们发现,数列的规律和青蛙跳台阶是一样的,同样可以有如下猜测。
f(n) = f(n-1) + f(n-2)
如果用递归的思路,我们则可以更清晰地得到如上的结论。
以f(4)、f(5)、f(6)为例
f(4) = 3,[A,Aa,Ab],
f(5) = 5,[A,Aa,Ab,Ac,Aa1]
f(6) = 8 [A,Aa,Ab,Ac,Ad,Aa1,Aa2,Ab1] ……
f(6),也即第6个月中的兔子由2部分组成
蓝色的兔子:上个月存在的兔子,也即f(5)中的兔子,在这个月也一定存在。
红色的兔子:上上个月存在的兔子,也即f(4)就存在的兔子,在这个月一定会再生出一对新的兔子。A生了Ad,Aa生了Aa2,Ab生了Ab1。
所以,f(6) = f(5) + f(4)
对于所有的f(n),也都成立,也即f(n) = f(n-1) + f(n-2)
由于兔子数列最早是由意大利数学家斐波那契提出来的,所以也称为斐波那契数列。
有趣的是,这样一个完全是自然数的数列,通项公式却是用无理数来表达的。而且当n
趋向于无穷大时,前一项与后一项的比值越来越逼近黄金分割0.618(或者说后一项与前一项的比值小数部分越来越逼近 0.618)。
越到后面,
的比值越接近黄金比,所以斐波那契又称为黄金分割数列。
更为神奇的是,自然界有非常多的现象都呈现出斐波那契数列的特征,比如比如松果、凤梨、树叶的排列、某些花朵的花瓣数(典型的有向日葵花瓣),蜂巢,蜻蜓翅膀等等,有兴趣的话可以百度搜索。
汉诺塔
法国数学家爱德华·卢卡斯曾编写过一个印度的古老传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,有三根神柱。印度教的主神梵天在创造世界的时候,在其中一根柱子上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根柱子上,小片必须在大片上面。僧侣们预言,当所有的金片都从梵天穿好的那根柱子上移到另外一根柱子上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。
问题是:假设有3个柱子:A,B,C和n片金片,如果要将全部金片按如上规则从A柱移动到C柱,请问该如何移动?
更多文章关注公众号,和孩子一起学Python