背景
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。(源自百度百科)
一、汉诺塔和递归
当我们想将64个圆盘从A柱移动到C柱上,我们可以将其分为三个步骤:
步骤1、通过一种符合要求的方式将A柱上63个圆盘从A移动到B(我们不要关心这个方式具体是什么)
步骤2、经过步骤1之后A柱上只剩下一个圆盘,我们将这个圆盘从A移动到C(我们所要关心的只有最后将1个圆盘从A移动到C)
步骤3、经过步骤1和步骤2之后,B柱上有了63个圆盘,C上有1个圆盘,我们再通过某种方式将B柱上63个圆盘从B移动到C(同样也不要关心这个具体的方式是什么)
经过了步骤1、 2 、3之后,我们就将所有的圆盘从A移动到了C
但是问题又来了,在步骤1中,我们将63个圆盘从A移动到了B,那么我们如何移动呢?
步骤1又可以继续拆分:
步骤1.1、将62个圆盘从A柱移动到C柱
步骤1.2、将1个圆盘从A柱移动到B柱
步骤1.3、将62个圆盘从C柱移动到B柱
步骤1.1又可以继续拆分:
1.1.1、将61个圆盘从A柱移动到B柱
1.1.2、将1个圆盘从A柱移动到C柱
1.1.3、将61个圆盘从B柱移动到C柱
继续向下拆分
.
.
.
经过63次拆分后,第64次只需要移动1个圆盘,只需要将1个圆盘移动到领一个圆盘上即可
我们设一个函数H(n)表示n个圆盘从A柱移动到C柱需要移动多少次
我们要先将(n-1)个圆盘从A移动到B柱,需要移动H(n-1)次
然后将1个圆盘从A移动到C柱,需要移动1次
再讲(n-1)个圆盘从B柱移动到C柱,需要移动H(n-1)次
因此H(n) = 2*H(n-1) +1,H(1)= 1;
我们就得到了递推公式
H(1) =1=2^1-1
H(2)=2(H(1))+1=2^2-1
H(3)=2(H(2))+1=2^3-1
H(4)=2(H(3))+1=2^4-1
.
.
.
H(n)=2^n-1。
所以64个圆盘需要移动2^64-1次,假设1秒移动可以移动1次,差不多需要5845.42亿年以上。
对于最初的三个步骤,可能很多人还是比较迷糊,我们换一个说法,现在小明有一个任务,就是将64个 圆盘从A移动到C,但是他只会移动一个圆盘。于是他找到了小张帮忙,小张欣然接受 ,并保证完成任务,于是小张帮小明把上面的63个圆盘从A移动到B,然后小明再将第64个圆盘移动到C;等小明移动完第64块圆盘之后,再让小张把63个圆盘 从B移动到C。对于小明来说,他不用关心小张是如何做到将63个圆盘从A移动到B,再从B移动到C,他只需要等小张移动完63块圆盘之后,完成第64块圆盘的移动。而小张在进行移动的时候,他可以再找其他人帮助,这些小明都不用关心,同样小张也不用关心他所找的人是如何进行的移动。
我觉得这就像是A公司有一个大项目,A公司将这个项目拆分成了2个部分,将最核心的部分留给自己公司处理,然后将剩下的部分外包给B公司,B公司同样可以像A公司一样,继续往下外包,这样一直下去。只要最后每个公司能保证属于自己的部分完成,那么整个项目就可以顺利的完成。
二、代码实现
#include<stdio.h>
void Move_(char From, char Dest) //移动一个圆盘,将圆盘从来源移动到目的地 从From 移动到Dest
{
printf("将一个圆盘从%c柱子 -> %c柱子\n", From, Dest);
}
void Hanoi( char A,char B,char C,int n) //总共有n个圆盘,将这n个圆盘 借助 B 柱子 从 A 柱子移动到 C 柱子
{
if (n == 1) //当只有一个圆盘时,直接圆盘从 A 柱 移动到 C 柱
{
Move_(A, C);
}
else
{
Hanoi(A,C,B,n - 1); //当不只一个圆盘时,我们先将上面 (n -1)个圆盘 借助 C柱子 从 A 柱子移动到 B 柱子
Move_(A, C); //A柱剩余一个圆盘,将剩下的一个圆盘从 A 移动到 C
Hanoi(B, A, C, n - 1); //再将(n-1)个圆盘 借助 A柱子 从 B柱子 移动到 C柱子
}
}
int main()
{
int n = 0; //汉诺塔层数
char A = 'A'; //A柱子
char B = 'B'; //B柱子
char C = 'C'; //C柱子
scanf("%d", &n);
Hanoi(A,B,C,n); //将n个圆盘,借助于B柱子,从A柱子移动到C柱子
return 0;
}
运行结果:
n=3
n=5
n=20的时候,我的电脑运行了47秒的时间,根据上面的迭代公式,总共迭代了2^20-1次,≈ 2^ 20次,所以当n=30的时候需要迭代2^30次(减1忽略不计),大概需要
47* 2^10 =48128S,约等于13.37小时,当n=31的时候,大概需要13.37*2小时,约等于1天(我们大胆省略多出来的2个多小时)n=64的时候,需要迭代2^ 64次,大概需要2^33天。额,大概我是等不到那一天的。
总结
理解汉诺塔问题的关键就是要理解它的递归思想,以及只关心自己的任务,分派出去的任务只用关心它最后的结果,至于过程则不要去过多地关注
我的博客中还有一篇关于函数递归的问题:青蛙跳台阶如果有兴趣也可以看看
最后感谢各位的阅读,如果觉得有用,请帮忙点个赞吧