您的当前位置:首页正文

编程递归汉诺塔,3步分解,3分钟学会

2024-11-08 来源:个人技术集锦

如果要理解什么是递归,那么汉诺塔是一个非常经典的例子。几乎所有的教科书都会提到汉诺塔。

汉诺塔的传说

汉诺塔有一个非常有趣的传说。

也就是说,当僧侣把64片金片从左边按照规则移动到最右边时,这个世界就会毁灭。

读到这里先不要紧张,让我们来分析看看这位僧侣有没有可能真的把64片金片都移到右边去。

定义银针

首先,我们可以给三个银针进行分类:

  • 起始银针
    金片的起始位置。

  • 过渡银针
    中间暂放其他金片的过渡位置。

  • 目标银针
    最终放置金片的目标位置。

3步分解汉诺塔步

首先,我们可以先将解开汉诺塔的步骤分解,分解步骤可以帮助我们简化问题。这里我们以5片汉诺塔为例:

我们都知道,如果要将金片全部移动到最右边(目标银针),我们首先得把第5片金片(最下面的)先移动到最右边,因为其他可以移动的金片都比它小,所以不能放在它的上面。

第一步

那么,若要将第5片金片移到目标银针,我们首先还得把第1,2,3,4片金片都挪到过渡银针上,如下图:

第二步

接着,我们可以将第5片金片移动到目标银针上面:

第三步

然后我们再把过渡银针上面的金片移动目标银针上面就可以了。但注意,此时对于过渡银针上的金片来说,起始银针与过渡银针的角色互换了。

总结规律

每次我们要将第n个金片移动到目标银针时,都需要

以此类推,当我们准备将上方n-1个金片挪到过渡银针上时,对于第n-1个金片来说,之前的过渡银针就是它的目标银针,而我们得先将上方的n-2个金片移动到另外一个过渡银针上面。在那之前,我们得把上方n-3个金片给…

由此可见,这是一个递归过程,不停地将问题数量缩小,并且每一个阶段的逻辑都是一样的。

因此,我们可以将代码写成递归的方式,只是在每一次递归调用的时候,起始银针,过渡银针以及目标银针的角色总是会产生变化,因为对于不同阶段的不同金片来说,他们的起始,过渡以及目标银针是不一样的。

参考代码 Python

注意每一次调用递归函数的时候,银针每次都被作为不同角色的参数传入,这表示对于每一个金片来说,银针的角色都在变化。

# 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步分解:

  1. 将n-1个金片移动到过渡银针需要x步
  2. 将第n个金片移动到目标银针需要1步
  3. 将n-1个金片从过渡银针移动到目标银针需要x步(同第一步需要一样的步数)

我们可以得到移动n个金片最少需要步数:
x + 1 + x = 2x + 1

也就是说,每增加一块金片所增加的步数,都是前一片金片步数的两倍加上一步。

我们可以为此制定一张表来看看,我们知道只有一片金片的时候步数是1:

金片数量1234564
步数13715311.84 * 10^19

公式为:
2^n - 1(n为金片数量)

由此可见,若是要把64片金片移动到最右边去,那需要的步数可是个天文数字啊!就是1秒钟移动一片,到宇宙毁灭的那天也没办法完成。

所以,我们不必担心当64片金片都移动到右边的时候这个世界会毁灭,因为在那之前,宇宙已经先毁灭了。

Top