一、汉诺塔简介
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定: 1.在小圆盘上不能放大圆盘 2.在三根柱子之间一次只能移动一个圆盘。
二、分析汉诺塔规则并设计函数(以C语言为例)
2.1 分析规则
假设有三根柱子,暂且把它们分别称为A、B和C,并且初始情况是A上有n个圆盘,想要按照规则,将所有圆盘移动到C上。那么B圆盘有什么作用呢?我们可以把它理解为中转站。 举个例子:张三家乡是上海,有一天他想坐飞机去新疆游玩,于是他看了看机票,发现没有直达的机票,但是中转的票有很多。没办法,张三只能买中转票,权衡之下买了一张上海到北京再到新疆的机票。在这个例子里,上海就相当于柱子A,新疆就相当于C,北京就相当于B,而做飞机其实可以理解为汉诺塔的规则:张三给自己定的规则就是坐飞机去,而你想玩汉诺塔,就必须遵守在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘的规则。 如果没有北京这个中转站,张三就无法坐飞机去新疆。同理如果没有B这个中转柱子,就无法遵循汉诺塔规则,试想只有两根柱子A和C,A上有一大一小两个圆盘,这样无论怎样移动,都会违反其中的某个规则:若想小圆盘在大圆盘之上,只能一次将两个都移到C上,违反规则2;若一次只移动一个圆盘,那大圆盘必然会落在小圆盘上。因此B的存在是必要的,且只需要一个就可以完成任务(马上会介绍原因)。
2.2 函数设计思路
1.先来分析函数的返回值以及参数列表。 通过上边的分析,我们知道B只是一个中转站,可以理解为A上的圆盘通过B到达C。因此函数至少3个参数:A、B和C。另外我们需要知道A上有多少个圆盘,可以实现任意圆盘数量n的移动,因此函数共需要4个参数:char A,char B,char C,int n. n为int型不难理解,因为需要表示数量,那为什么A、B和C要用char类型呢?原因很简单,我们定义的函数要是实现的功能是表示出圆盘从A借助B到C的移动路径,比如:
只有一个圆盘的时候:A --> C 有两个圆盘的时候:A --> B、A --> C、B --> C
因此定义两个简单的字符用于表示路径即可。也不需要什么返回值,直接将路径打印出来即可,因此返回值为void。至此,函数的返回值,参数列表都已确定:
void hanoi(char A,char B,char C,int num){}
翻译一下:A上的n的圆盘通过B放到C上,记住这个表述,后边会用到。
2.接下来就是完善函数体 当圆盘数量为1的时候,直接将A上的1个大圆盘移动到C上即可; 当圆盘数量为2的时候,要先将 A上的小圆盘通过C移动到B,这时A上只剩下1个大圆盘,再将A上的大圆盘移动到C,最后将B上的小圆盘移动的C ; 当圆盘数量为3的时候,先把A上的上边两个较小的圆盘通过C移动到B,这时A上只剩下1个大圆盘,再将A上的大圆盘移动到C,最后在将B上的两个较小的圆盘通过A移动到C。 当圆盘数量为4的时候,先把A上的上边三个较小的圆盘通过C移动到B,这时A上只剩下1个大圆盘,再将A上的大圆盘移动到C,最后在将B上的三个较小的圆盘通过A移动到C。 当圆盘数量为5的时候,先把A上的上边四个较小的圆盘通过C移动到B,这时A上只剩下1个大圆盘,再将A上的大圆盘移动到C,最后在将B上的四个较小的圆盘通过A移动到C。 注意上边n大于等于2的时候相同颜色的字,我们可以发现很多相同的操作(n小于2时有足够的位置,直接移动即可): 首先是将A上的盘子分为了两部分,第一部分是最下边的大盘子,第二部分是其余的所有小盘子。 第一步都是将A中除了最大的盘子以外的盘子通过C移动到B上; 第二步都是将A中剩下的最大的盘子移动到C上; 第三步都是将B中的所有盘子通过A放到C上。 而其中的绿色和红色的部分,有没有觉得很熟悉,没错,就是上面我对定义的函数的黄色翻译。 因此这两个部分可以调用这个函数本身,也就是函数的递归。如果还不理解递归,那么再拿出开头的例子:这次张三想要坐飞机从上海到纽约,但是没有直达的机票,于是他找到了一个中转站洛杉矶,但是上海到洛杉矶也没有直达的票,所以他在上海和洛杉矶之间找到了一个中转站-北京。因此,他从上海到纽约的方式和他从上海到洛杉矶的方式都是中转到达,只不过上海到洛杉矶这次中转发生在从上海到纽约这次大的中转之中,如果翻译成代码就是:
中转(上海,洛杉矶,纽约,张三)
{
中转(上海,北京,洛杉矶张三)
}
对比void hanoi(char A,char B,char C,int num){}
在第一个中转中:A--上海 B--洛杉矶 C--纽约 n--张三
在第二个中转中:A--上海 B--北京 C--洛杉矶 n--张三
那么现在只剩下蓝色部分,可以看到,制作了一次简单的移动,因此定义一个函数,打印出移动路径:
void move(char A,char B)
{
count++;
printf("第%d次移动:%c --> %c\n",count,A,B);
}
至此,设计思路已完成,下面直接翻译成代码。
三、代码实现与分析
3.1 代码实现
int count = 0;
void move(char A,char B)
{
count++;
printf("第%d次移动:%c --> %c\n",count,A,B);
}
void hanoi(char A,char B,char C,int num)
{
if (1 == num)
{
move(A, C);
}
else
{
hanoi(A, C, B, num - 1);
move(A, C);
hanoi(B, A, C, num - 1);
num--;
}
}
int main()
{
int n = 0;
printf("请输入汉诺塔的层数:");
scanf("%d", &n);
hanoi('A', 'B', 'C', n);
printf("共移动%d次\n", count);
}
运行结果:
3.2 分析
简单分析一下代码的运行次数:
1个圆盘:1次(从A到C) = 2^1 - 1
2个圆盘:3次(将下边的大圆盘和上边的小圆盘分别看作两个整体,最开始上边的整体从A到B以及最后从B到C都可以看作是和1个圆盘相同的情况.
即2*(1个圆盘的情况)+ 1(中间一步的大圆盘从A到C)) = 2^2 - 1
3个圆盘:7次(将下边的大圆盘看作一个整体,上边的两个小圆盘看作一个整体,单独看上边的两个小圆盘,把他们从A搬运到B其实就是上一条2个圆盘
的情况(3次),只不过目的地从C变成了B;最后B上的两个小圆盘搬运到C上也是同样的情况(3次),再加上中间从A直接搬运到C(1次).
即2*(2个圆盘的情况)+ 1(中间一步的大圆盘从A到C)) = 2^3 -1
以此类推……
n个圆盘:2^n-1 次
连写四个小时终于写完了,欢迎留言讨论。
|