汉诺塔问题
1. 汉诺塔传说
印度教的主神梵天在创造世界的时候创造了三根塔座,在其中一根塔座上从下到上地穿好了由大到小的64个盘子,这就是所谓的汉诺塔。主神梵天命令一个僧侣不论白天黑夜按照下面的法则移动这些盘子:一次只移动一个盘子,不管在哪根塔座上,小盘子必须在大盘子上面。僧侣们预言,当所有的盘子都从梵天穿好的那根塔座上全部移到另外一根塔座上时,世界将会灭亡。
2. 问题描述
塔座A、B、C。开始时A塔座上有64个盘子,大小不一,大盘在下,小盘在上。有一个僧侣想将A塔座的64个盘子全部移动到C塔座上,假设他一秒钟移动一个,且不分白天黑夜,一年有三百六十五天,那他多长时间能完成这个任务,且有如下规定:
1. 一次只能移动一个圆盘
⒉必须始终保持大盘在下,小盘在上
⒊圆盘只能在3个塔座间移动(不允许放在地面上等其他地方)
3. 刚开始的思维过程
你也许可能会这样想:将A塔座最上方的小盘拿下来放在B塔座上或者C塔座上,假设放在B塔座上,再将A塔座最上方的小盘拿下来,按照规定这个小盘只能放在C塔座上(这个小盘比B塔座上的小盘要大,所以不能放在B塔座上),这个时候想再从A塔座上拿下来一个盘子,发现这个盘子比B塔座和C塔座上的盘子都要大,不能这样做,只能拿B塔座上的盘子,然后将它放在C塔座上。然后……,你会发现无论你的思维多么强大,你还是很快被绕晕,因为盘子太多了。
4. 深入思维过程
既然是因为盘子太多导致了思维无法进行下去,那只能将盘子的数量先减少看看,那索性从一个盘子开始。
1 盘:
可以将A塔座上的盘子直接放在C塔座上
2 盘:
可以很快想到移动方法:
1) 先将A塔座上的蓝色盘子放在B塔座上
2)再将A塔座上的绿色盘子放在C塔座上
3)再将B塔座上的蓝色盘子放在C塔座上
3盘:
当有三个盘子时,这时不能很快想到移动的方法,需要先试探一下。先将黄色盘子放在B塔座上还是C塔座上,目前不能决定,那先测试一下先放在B塔座上,不过先记下这个分歧。
1) 先将A塔座上的黄色盘子放在B塔座上
2) 再将A塔座上的蓝色盘子放在C塔座上
3) 这时B塔座上的黄色盘子,按照正常思维放在C塔座上
这时再看塔座上的盘子发现塔座上的盘子摆不下去了,记得第一步时放黄色盘子时有一个分歧:将黄色盘子先放在B塔座上还是C塔座上,经过测试,将黄色盘子先放在B塔座上不行,只能先放到C塔座上。
4) 将第一步中的将黄色盘子改放在C塔座上,再将蓝色盘子放在B塔座上,再将黄色盘子放在B塔座上,有如下移动效果:
5)将A塔座上的绿色盘子放在C塔座上
6)这时很快就能想到下面的移动方法,先将黄色盘子放在A塔座上,再将蓝色盘子放在C
塔座上,然后再将黄色盘子放在C塔座上。
4 盘:
1) 这时我们的思维有点迟疑了,不知怎么移动。我们上面只是实现了1盘、2盘、3盘移动。如果只移动3个盘子,还好说,还能移动到C塔座上:
2) 如果再将A塔座上的绿色盘子放在B塔座上
3) 如果再将C塔座上的三个盘子放在B塔座上,当前这需要一步一步操作。
我们很惊讶的发现A塔座上所有的盘子移动到B塔座上了,如果我们一开始将A塔座上前三个盘子移动到B塔座上(怎么一步一步移动完成遵照3盘方法),再将A塔座上最底的盘子移动到C塔座上
4) 再将B塔座上的三个盘子移动到C塔座上,就完成了4盘子的任务
5 盘:
1) 经过上面的移动,我们发现可以先将A塔座上的前4个盘子移动到B塔座我们发现可以先将A塔座上的前4个盘子移动到B塔座上(怎么一步一步移动完成遵照4盘方法),再将A塔座上的最底的盘子移动到C塔座上
2)再将B塔座上的四个盘子移动到C塔座上(怎么一步一步移动完成还是要遵照4盘方法)
……
64 盘:
根据5盘的解决方法,我们可以想到如下的解决方法:如果移动A塔座上的64个盘子到C塔座上,可以先移动A塔座63个盘子到B塔座上,再将A塔座最底的盘子移动到C塔座上,然后再将B塔座上63个盘子移动到C塔座上。
如何将A塔座上的63个盘子到B塔座上?可以先将A塔座上的62个盘子移动到C塔座上,再将A塔座上的第63盘子移动到B塔座上。再将C塔座上的62个盘子移动到B塔座上。
如果想将A塔座上的62个盘子移动到C塔座上,则需要先将A塔座上的61个盘子移动到B塔座上,以此类推。
5. 总结:
有三个塔座,如果想将一个塔座上的N个盘子移动到另一个塔座上,则需要先将N-1个盘子移动到第三个盘子上。再将最底的盘子移动到另一个塔座上,然后再将第三个盘子上的N-1个盘子移动到另一个塔座上。
6. 程序逻辑
计算移动A塔座上n个盘子到C塔座上,需要先计算A塔座将n-1个盘子移动到B塔座上的时间,加上将A塔座上最底的盘子移动到C塔座上的时间,再加上将B塔座上n-1个盘子移动到C塔座上的时间。以此类推, 这与函数的递归调用逻辑不谋而合,所以这道题需要用到递归函数来解决。
我在上一篇博客写到再写递归函数时需要确认两点:1. 递归出口,2. 递归逻辑
1. 递归出口
n == 1时
A塔座移动最底的盘子到C塔座上。时间为1。
2. 递归逻辑
n != 1时,需要三个步骤的时间
A塔座移动n – 1个盘子到B塔座上的时间
A塔座移动第n个盘子到C塔座上的时间,时间为1
B塔座移动n – 1个盘子到C塔座上的时间
7. 递归函数编写
1)函数名:hanoi_time
2)参数列表:需要int n 表示移动A塔座上n个盘子
3) 返回值 int 代表时间
函数如下:
#include <stdio.h>
int hanoi_time(int n);
int main(int argc, const char *argv[])
{
inttime, times;
puts("Inputtimes!");
scanf("%d",×);
time= hanoi_time(times);
printf("time= %d\n",time);
return0;
}
int hanoi_time(int n)
{
if(n == 1)
{
return1;
}
else
{
return(hanoi_time(n - 1) + 1 + hanoi_time(n -1));
}
}
经过测试我的计算机算不出来n = 64时的时间,可见其中的运算量是如此的庞大。测试一下n为前几个数计算步骤都是正确的。
8. 问题扩展
将所有的移动步骤打印出来
移动A塔座上n个盘子到C塔座上,需要先移动A塔座上n-1个盘子到B塔座上,然后将A塔座上最底的盘子移动到C塔座上,再将B塔座上n-1个盘子移动到C塔座上。程序框架与计算任务的总时间程序框架一样。
1. 递归出口
n == 1时
步骤:A塔座移动最底的盘子到C塔座上。move A -> C
2. 递归逻辑
n != 1 时
分为三个步骤
A塔座移动n – 1个盘子到B塔座上
A塔座移动第n个盘子到C塔座上
B塔座移动n – 1个盘子到C塔座上
递归函数编写
1)函数名:hanoi_move
2)参数列表:需要int n 表示移动A塔座上n个盘子,char A, char B,char C分别表示塔座A、B、C
3)返回值 void,不需要返回值
函数如下:
#include <stdio.h>
int hanoi_move(int n, char A, char B, char C);
int main(int argc, const char *argv[])
{
int times;
puts("Input times!");
scanf("%d",×);
hanoi_move(times, 'A', 'B', 'C');
return 0;
}
int hanoi_move(int n, char A, char B, char C);//请注意顺序,A是源,C是目的
{
if(n == 1)
{
printf("move%c -> %c\n",A, C);
}
else
{
hanoi_move(n-1,A, C, B);// A塔座移动n – 1个盘子到B塔座,A是源,B是目的
printf("move%c -> %c\n",A, C);
hanoi_move(n-1,B, A, C); // B塔座移动n – 1个盘子到C塔座上,B是源,C是目的
}
}
注意:int hanoi_move(int n, char A, char B, char C); 该函数表示从字符A代表的塔座移动n个盘子到字符C代表的塔座,字符A并不一定代表塔座A,字符C并不一定代表塔座C。