汉诺塔问题
汉诺塔问题是一个经典的问题。汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。问应该如何操作?并求出最少移动次数。
分析问题
如果是初次接触类似的问题,乍看之下肯定会感觉无从下手。
为解决这个问题,首先我们将盘子由小到大编号1~n,第一步可以确定的是:移动编号1的盘子,但究竟移到b还是c?很难确定,因为接下来第二步、第三步,直到最后一步都很难确定,但因为要求移动次数最少,我们可以知道编号n的盘子移动情况:由a移到c,此前编号1~n-1的盘子(它上面的盘子)已经全部移到了b上。那么新的问题又来了:如何将前n-1个盘子移到b上?
仔细一想,我们发现新问题和初始问题如出一辙,都是将多少个盘子由a移到目标柱上(初始问题是要移到c盘,新问题是要移到b盘),那么我们是怎样思考初始问题的?我们考虑的是编号n的盘子(也就是最底部的盘子)移动情况,因此此时我们就考虑最底部编号n-1的盘子,可以知道:编号n-1的盘子由a移到b,此前编号1~n-2的盘子全部移到c上。
要解决编号1~n-2的盘子移动问题,是不是又要考虑最底部n-2的盘子移动情况,由此又要解决编号1~n-3的盘子移动到b上的问题。看到这儿,是不是有点思路了!是的,再往前追溯,一直到第一个盘子,我们是不是就解决了第一个盘子移到b还是c的疑问。规律是:n为奇数,移到c;n为偶数,移到b。
解决问题
通过以上分析,我们知道了解决这个问题的关键是:将关注点放到最底部的盘子,然后再将它上面的盘子看作一个整体,以此类推。
但当n较大时,我们还是不能清楚每一步。重新整理思路,当我们最开始考虑编号n的盘子时,此前n-1个盘子移到b上,此后我们应该将编号n的盘子由a移到c,再将b上n-1个盘子由b移到c,此时移动结束。当我们考虑n-1个盘子如何移到b上时:先将n-2个盘子由a移到c上,再将编号n-1的盘子由a移到b,最后将n-2个盘子由c移到b。再往前追溯,我们发现:此前所有移动都是这三个步骤(第一步除外),也就是说此时我们可以具体到每一步,这就是我们解决这个问题的通道,那么这个通道怎样结束了?很明显是第一步,因为第一步只用考虑将第一个盘子移到b或c上,而且答案我们是清楚的。
程序设计
定义hanoi函数使用递归实现具体步骤:
void hanoi(int n,char x,char y,char z)
{ //将盘子从小到大编号1~n,函数表示将编号1~n的盘子从x柱移到z柱
if(n==1) move(x,1,z);//将编号1的盘子由x移到z
else
{ hanoi(n-1,x,z,y);//将x上编号1~n-1的盘子由x移到y
move(x,n,z);//将x上编号n的盘子由x移到z
hanoi(n-1,y,x,z);//将y上编号1~n-1的盘子由y移到z
}
}
move函数记录移动过程和移动次数,如下:
void move(char x,int n,char z)
{ printf("%c->%c\n",x,z);
m++;
}
完整代码
#include<stdio.h>
int m=0;//定义初始化全局变量m
void move(char x,int n,char z)
{ printf("%c->%c\n",x,z);
m++;
}
void hanoi(int n,char x,char y,char z)
{ //将盘子从小到大编号1~n,函数表示将编号1~n的盘子从x柱移到z柱
if(n==1) move(x,1,z);//将编号1的盘子由x移到z
else
{ hanoi(n-1,x,z,y);//将x上编号1~n-1的盘子由x移到y
move(x,n,z);//将x上编号n的盘子由x移到z
hanoi(n-1,y,x,z);//将y上编号1~n-1的盘子由y移到z
}
}
int main()
{ int n;
printf("请输入圆盘总个数:\n");
scanf("%d",&n);
printf("圆盘移动过程如下:\n");
hanoi(n,'a','b','c');
printf("圆盘移动次数:%d\n",m);
return 0;
}
注:该程序的时间复杂度为O(2^n),移动步数为2的n次方-1,也就是说n的值不宜过大(建议<=20).如果盘子数量为64的话,你一共需要移动约1844亿亿步(18,446,744,073,709,551,615)才能最终完成整个过程。这是一个天文数字,没有人能够在有生之年通过手动的方式来完成.即使借助于计算机,假设计算机每秒能够移动100万步,那么也需要18万亿秒,即58万年。这也是为什么有人断言:当人们搬完这64个圆盘时,世界末日就要到来了。
代码执行过程
n=3时,代码执行过程如下(注意实参的变化):
运行结果