汉诺塔(Hanoi)
多图警告!
历史背景
法国数学家爱德华·卢卡斯曾编写过一个印度的古老传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。
不管这个传说的可信度有多大,如果考虑一下把64片金片,由一根针上移到另一根针上,并且始终保持上小下大的顺序。这需要多少次移动呢?这里需要递归的方法。假设有n片,移动次数是f(n).显然f(1)=1,f(2)=3,f(3)=7,且f(k+1)=2*f(k)+1。此后不难证明f(n)=2^n-1。n=64时,假如每秒钟一次,共需多长时间呢?一个平年365天有31536000秒,闰年366天有31622400秒,平均每年31557600秒,计算一下:18446744073709551615秒(以上摘自百度)
问题解析
首先汉诺塔的样子是这样的:
- 现在就需要我们解决这个问题,思考一下,如何将A上的所有小片移动到C上呢。这里就需要了解规则,首先每次只能移动一片,还有就是小片在上大片在下,这两个规则不明白的可以去链接里的小游戏自己去试一下 汉诺塔小游戏 。
一层汉诺塔
现在知道了规则,如何实现想要的效果呢,首先画一张图(画的比较抽象,见谅哈)。
- 一层的时候其实很好解决,只需要把A移动到C上就OK啦。
两层汉诺塔
现在我们来看两层的情况,同样做个图。
- 其实也是非常简单的,首先让小片移动到B上,再将大片移动到C上,再把小片从B移动到C上就完成了。(A->B , A->C , B->C)
- 建议反复在大脑中模拟移动过程,对后面的更多层的移动有好处。
三层汉诺塔
有三层的情况下怎么办呢,我们仍旧是做一个图,这次标上号。
三层的稍微有一丢丢的难度,如果不明白规则的可以去上面的链接里玩几次,就明白规则了。
- 如何将三片从A柱移动C柱上呢?首先是要让P1到C柱上,思考一下,为什么呢? 其实道理很简单,如果C柱上有其他的片,那么P1是不可能移动到它上面的,因为P1是最大的这一片。如果看不明白反复读几遍,脑子里模拟一下样子。
- 理解这个问题后就可以换位思考一下,既然要移动P1,那么P2和P3是不是就必须在B柱上呢。那么现在问题就变成了:如何把P2和P3移动到B柱上的问题。
- P2和P3这两片的问题,是不是非常类似于前面的两层汉诺塔问题,确实与两层的时候简直一毛一样!我们先将P3移动到C柱上,再把P2移动到B柱上,再把C柱上的P3移动回P2上,就完成了移动到B柱的任务。
如果你不能理解,那我再换一个方法:
- P1是最大的片,那么换言之,任何片都可以放在P1上,也就可以变相的理解为P1现在是不存在的。只有P2和P3,那么就回到两层的问题上,我们最基础的移动是把两片移动到最远的地方,也就是移动到C柱上,那么我把BC柱子换个位置。是不是就是一样了。柱子(A-C-B)这样摆放。
- 接下来呢就是把A柱上的P1移动到C柱上,这个没啥好解释的,我们的目的就是A->C的移动。
到现在A柱上为空,B柱上有P2和P3,C柱上有P1。
- 接下来就是把B柱子上的P2,P3移动到C柱上,同样的思想,把AB柱换位,P1忽略。是不是就又变成最简单的两层汉诺塔的移动了呢。这样我们就达到了目的,将A柱上的所有片都移动到了C柱上。柱子(B-A-C)这样摆放。
至此三层汉诺塔大功告成!
规律总结
-
如果有4层5层…一直到n层呢,总不能一直这样写下去吧。
-
咱们观察其中的规律,每次的移动其实都是先让A柱上的n-1片,先移动到B柱上,再把第n片移动到C柱上,再把B柱上的n-1片移动到C柱上即完成功能。
话不多说,直接上图:
- 一层一层的拆解,想要移动n-1层的片到B柱上,就需要搬走n-2片到C柱上…这句话比较难理解,多想几次,就能明白了。同理我们把B柱上的n-1片移动到C柱上就完成了。
- 我们不难发现,每次的循环都是同样的动作,这就刚好是计算机喜欢干的事情。我们就可以用递归来解决这个问题!
代码如下:
// 例子7.7(Hanoi汉诺问题)
int main() {
void hanoi(int n, char one, char two, char three);
int m;
scanf("%d", &m);
hanoi(m, 'A', 'B', 'C');
return 0;
}
//n为片数,ABC代表A柱B柱C柱,含义是:把A柱上的n片,借用B柱移动到C柱上
void hanoi(int n, char A, char B, char C) {
void move(char x, char y);
if (n == 1) move(A, C); //递归出口(退出的条件,不可缺少哦!)
else {
hanoi(n - 1, A, C, B); //步骤一 按ACB顺序执行N-1的移动
move(A, C); //步骤二 执行最大盘子移动
hanoi(n - 1, B, A, C); //步骤三 按BAC顺序执行N-1的移动
}
}
void move(char x, char y) {
printf("%c->%c\n", x, y); //此处的移动并未真实移动,实际上是输出步骤而已
}
其实我当时最迷惑的是为啥每次都是A移动到C,但是每次移动的步骤不一样呢,这里我解释一下,因为在else中.也就是步骤一那里,在柱子(虚实结合处)进行了交换,每次虽然都是执行最简单的移动,但是每次移动的方向不一样,所以移动的结果也就不一样。
相关链接
如果你喜欢较个真,想看更加深层次的递归过程:
链接挂在这里,里面有解析,也有递归的过程,讲得很细致,建议倍速一哈:
懒猫老师 C语言 汉诺塔问题解析 15分15秒处开始深层次解析
还有其他几个Up我觉得讲的很不错,大家可以参考一下:
李永乐老师 汉诺塔问题解析
国外大佬的解析 以二进制(位运算)的角度解析,含有动画!!!
以上呢就是我对问题的理解,大家发现错误或者觉得有疑问可以留言或者私信,欢迎大家讨论。(本人菜鸡,大神别嫌弃我哈哈哈)