如果要理解什么是递归,那么汉诺塔是一个非常经典的例子。几乎所有的教科书都会提到汉诺塔。
汉诺塔有一个非常有趣的传说。
也就是说,当僧侣把64片金片从左边按照规则移动到最右边时,这个世界就会毁灭。
读到这里先不要紧张,让我们来分析看看这位僧侣有没有可能真的把64片金片都移到右边去。
首先,我们可以给三个银针进行分类:
起始银针
金片的起始位置。
过渡银针
中间暂放其他金片的过渡位置。
目标银针
最终放置金片的目标位置。
首先,我们可以先将解开汉诺塔的步骤分解,分解步骤可以帮助我们简化问题。这里我们以5片汉诺塔为例:
我们都知道,如果要将金片全部移动到最右边(目标银针),我们首先得把第5片金片(最下面的)先移动到最右边,因为其他可以移动的金片都比它小,所以不能放在它的上面。
那么,若要将第5片金片移到目标银针,我们首先还得把第1,2,3,4片金片都挪到过渡银针上,如下图:
接着,我们可以将第5片金片移动到目标银针上面:
然后我们再把过渡银针上面的金片移动目标银针上面就可以了。但注意,此时对于过渡银针上的金片来说,起始银针与过渡银针的角色互换了。
每次我们要将第n个金片移动到目标银针时,都需要
以此类推,当我们准备将上方n-1个金片挪到过渡银针上时,对于第n-1个金片来说,之前的过渡银针就是它的目标银针,而我们得先将上方的n-2个金片移动到另外一个过渡银针上面。在那之前,我们得把上方n-3个金片给…
由此可见,这是一个递归过程,不停地将问题数量缩小,并且每一个阶段的逻辑都是一样的。
因此,我们可以将代码写成递归的方式,只是在每一次递归调用的时候,起始银针,过渡银针以及目标银针的角色总是会产生变化,因为对于不同阶段的不同金片来说,他们的起始,过渡以及目标银针是不一样的。
注意每一次调用递归函数的时候,银针每次都被作为不同角色的参数传入,这表示对于每一个金片来说,银针的角色都在变化。
# numDisks: integer 当前起始银针上需要考虑的金片
# source_rod: [] 起始银针
# middle_rod: [] 过渡银针
# target_rod: [] 目标银针
def move_hanoi_tower(numDisks, source_rod, middle_rod, target_rod):
# 如果当前起始银针上需要考虑的金片只有1个的话,直接将其从起始银针移动到目标银针
if (numDisks == 1):
target_rod.insert(0, source_rod.pop(0))
else:
# 否则,先将上方的金片都移动到过渡银针。这里过渡银针被传入当成目标银针
move_hanoi_tower(numDisks-1, source_rod, target_rod, middle_rod)
# 然后将当前的金片直接从起始银针移动到目标银针
target_rod.insert(0, source_rod.pop(0))
# 接着,再将过渡银针上的金片移动到目标银针。此时,过渡银针作为这些金片的起始银针
move_hanoi_tower(numDisks-1, middle_rod, source_rod, target_rod)
通过将汉诺塔步骤分解为3步之后,我们便可以轻松计算出任意数量金片所需移动的最少步数了。
根据3步分解:
我们可以得到移动n个金片最少需要步数:
x + 1 + x = 2x + 1
也就是说,每增加一块金片所增加的步数,都是前一片金片步数的两倍加上一步。
我们可以为此制定一张表来看看,我们知道只有一片金片的时候步数是1:
金片数量 | 1 | 2 | 3 | 4 | 5 | … | 64 |
---|---|---|---|---|---|---|---|
步数 | 1 | 3 | 7 | 15 | 31 | … | 1.84 * 10^19 |
公式为:
2^n - 1(n为金片数量)
由此可见,若是要把64片金片移动到最右边去,那需要的步数可是个天文数字啊!就是1秒钟移动一片,到宇宙毁灭的那天也没办法完成。
所以,我们不必担心当64片金片都移动到右边的时候这个世界会毁灭,因为在那之前,宇宙已经先毁灭了。