问题背景:
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。 (来自百度百科)
问题解决:
首先,我们设置需要移动的圆盘数量为 n,三根柱子分别设置为A、B、C
其次,我们假定本次需要解决的问题是将圆盘从A柱子移动到C柱子上面。
当n = 1时:(如图所示)
我们需要做的只是将一个圆盘从A移动到C
表示为:
1, A->C
当n = 2时:(如图所示)
我们要做的是将两个圆盘从A移动到C,
分解一下步骤,我们先将最上面的圆盘从A移动到B,再将最下面的圆盘从A移动到C,最后将刚刚移动到B的圆盘从B开始移动到C,一共三步
表示为:
1, A->B
1,A->C
1,B->C
当n = 3时:(如图所示):
我们要做的是将三个圆盘从A移动到C
如果我们是第一次遇见汉诺塔问题,那么我们需要手动地,一步一步去尝试每次移动一个圆盘,然后在不打破规则的前提下去解决这个问题
如果你手动演算或者在大脑里演示了一遍的话,你会发现这个把三个圆盘成功从A移动C的过程一共需要7步
这并不难,但是当告诉你要移动4个、5个、6个圆盘时,你就要花费15步、31步、63步的时候
你发现,这个问题的难度和复杂程度就会随着要移动圆盘数量的增加而不断变大,这个时候再去手动演算或者在大脑里演示,就有点吃力不讨好了
所以我们必须要在这之中找到一些规律,或者发现一些比较关键的地方去更加巧妙的解决这个问题
让我们重新回到要解决移动三个圆盘的时候-------
我们不妨利用 n = 2时的经验 我们可以这样思考 接下来的操作:
一开始我们需要做的是将除了最下面的圆盘(把两个圆盘包装成一起)从A移动到B,再将最下面的圆盘从A移动到C,最后将刚刚移动到B的所有圆盘(仍然是包装好的)从B移动到C
模仿n=2的情形,我们又一次把将三个圆盘从A移动到C这个任务分成
一共三步
表示为:
step1. 2,A->B
step2. 1,A->C
step3. 2,B->C
如图:
其中,step1. (将除了最下面的圆盘从A移动到B时),我们需要分解步骤,并且解除我们刚刚的包装,把两个圆盘分开,因为我们根据一次性只能移动一个圆盘并且必须遵循更大的圆盘必须在稍小的下面这个原则
所以,我们把step1. 2, A->B 分解为:
把"除了最下面的圆盘中"--"除了最下面的圆盘"(也就是所有圆盘里排除了最大和第二大的圆盘后剩下的圆盘)从A移动到C,再把"除了最下面的圆盘中"--"最下面的圆盘"(也就是所有圆盘里第二大的圆盘)从A移动到B,最后再把C的圆盘移动到B
(可能听起来有点拗口,但是不妨多思考一下,这个过程是否与刚刚在n=2时做的分步操作很相似------如果你要将包装好的两个圆盘一起从一个柱子移动到另一个柱子那么这个过程可以分成三步,并且需要借助一个中间柱子来完成移动。这种相似很关键!!)
如图:
表示为:
分解1. 1,A->C
分解2. 1,A->B
分解3. 1,C->B
我们可以在这里获得一层抽象-abstraction,我们将刚刚分解的步骤
分解1. 1,A->C
分解2. 1,A->B
分解3. 1,C->B
包装成函数 move(2,A,C,B) 解释为:将 两个圆盘 从A先移动到C再借助C移动到B
其中移动圆盘数量为2,移动目标为从A->B,中间柱为C
同理 我们将 step3. 2,B->C 即”再将刚刚移动到B的所有圆盘从B移动到C“这个步骤也分解,并且解除包装:
如图:
借助刚刚的那种很关键的相似:如果你要将包装好的两个圆盘一起从一个柱子移动到另一个柱子那么这个过程可以分成三步,并且需要借助一个中间柱子来完成移动。
*在这里我们需要利用A作为中间柱
表示为:
分解1. 1,B->A
分解2. 1,B->C
分解3. 1,A->C
我们可以也在这里获得一层抽象,我们将刚刚分解的步骤
分解1. 1,B->A
分解2. 1,B->C
分解3. 1,A->C
即为:move(2,B,A,C) 解释为:将 两个圆盘 从B先移动到A再借助A移动到B
其中移动圆盘数量为2,移动目标为从B->C,中间柱为A
最终、我们用包装圆盘并抽象的方法简化了 当n = 3时的问题
表示为:
step1. move(2,A,C,B)
step2. 1,A->C
step3. move(2,B,A,C)
至此,我们成功地将三个圆盘从A柱子移动到C柱子的问题解决完毕
我们回顾一下我们解决问题的过程,发现
整个过程中,我们把移动三个盘子从A到C这个过程,巧妙的通过包装(抽象)变成了先移动两个盘子到B,再移动剩下的一个盘子到C,再移动刚刚的两个盘子到C
而我们再去试验一下,发现当n = 4时
存在:
move(3,A,C,B)
A->C
move(3,B,A,C)
如图:
整个过程中,我们把移动四个盘子从A到C这个过程,又一次巧妙的通过包装(抽象)变成了先移动三个盘子到B,再移动剩下的一个盘子到C,再移动刚刚的三个盘子到C
而每个包装起来的过程又可以用同样的道理拆分成更小的包装!三个盘子可以分解成两个盘子的移动!
这就是抽象的魅力!
恭喜你!到这里了你已经掌握了汉诺塔问题的关键!
------“ 递归 ”
你现在可以大胆猜测,当要移动 n 个盘子从A到C时,我们能把这个步骤先分成:
move(n-1,A,C,B)
A->C
move(n-1,B,A,C)
那么移动 n-1 个盘子就可以分解成
move(n-2,A,C,B)
A->C
move(n-2,B,A,C)
依此类推......
把移动较大数量圆盘的问题不停分解成移动更少数量圆盘的问题,直到最后移动圆盘的个数为两个时,问题又回到了当 n = 2 时 我们最熟悉的那个步骤
” 如果你要将包装好的两个圆盘一起从一个柱子移动到另一个柱子那么这个过程可以分成三步,并且需要借助一个中间柱子来完成移动。“
我们再次回到 n = 2 去想一想 如果我们把 原本 1, A->B 即 把一个圆盘从A移动到B这个步骤 也分解成,将圆盘先移动到C,再借助中间柱C移动到B(虽然在实际演示的时候并不会存在这个步骤,但我们可以假装存在这个步骤)
如图:
再把 原本 1,B->C 即 把在B上的一个圆盘从B移动到C这个步骤 分解成,将圆盘先移动到A,再借助中间柱A移动到C(同样这个步骤也不会实际存在)
如图:
那么我们可以发现
原步骤:
1, A->B
1,A->C
1,B->C
又会变成我们熟悉的
move(1,A,C,B)
A->C
move(1,B,A,C)
终于,你得到了并且成功再一次证实了一次 他们之间存在的相似,也就是这个规律:
“
从一个柱子A,移动n个(n>=2)圆盘到另一个柱子C时,都可以用以下式子表示过程:
move(n-1,A,C,B)
A->C
move(n-1,B,A,C)
”
现在我们开始尝试写一下代码:
C实现
#include <stdio.h>
int move(int n,char a,char b,char c)
{
if(n==1)
{
printf("%c->%c\n",a,c);
}
else
{
move(n-1,a,c,b);
printf("%c->%c\n",a,c);
move(n-1,b,a,c);
}
return 0;
}
int main()
{
int move(int,char,char,char);
int n,counter;
printf("Input the number of diskes:");
scanf("%d",&n);
counter= move(n,'A','B','C');
return 0;
}
Python实现
def move(n, a, b, c):
if n == 1:
print(a, '-->', c)
else:
move(n - 1, a, c, b)
print(a, '-->', c)
move(n - 1, b, a, c)
if __name__ == '__main__':
m = input("请输入圆盘数量:")
m = int(m)
move(m, 'A', 'B', 'C')
本博客使用的汉诺塔动画演示网站:汉诺塔