汉诺塔问题是一个古典的数学问题,也是c语言学习中一个用递归方法解题的典型实例,我们先看一下原题。
相传在古印度圣庙中,有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。
对于这样一个问题,我们第一印象就是十分的复杂,写出移动盘子的每一步似乎非常的困难,但我们可以利用下面的递归方法来解决。
首先分析,将n个盘子从A移动到C可以分解为三个步骤
(1)将A上的n-1个盘子借助C移动到B上;
(2)将A上剩下的一个盘子移到C上;
(3)将n-1个盘子从B移动到C上;
这对于写代码来说就变得十分简单了,但是依然很抽象,不过不要急,我们先把代码写出来再慢慢分析,
这是代码 其中hanoi函数有四个值,hano(n,A,B,C),代表着我们将盘子从A借助B移动到C上
#include <stdio.h>
void hanoi(int n,char X,char Y,char Z)//n个盘子在A上,借助B,移动到C上
{
if(n==1)//如果A上只有一个盘子,直接移动到C上
printf("%c-->%c\n",X,Z);
else//一旦多余一个,则执行递归程序
{
hanoi(n-1,X,Z,Y);//将A上的n-1个盘子,借助C,移动到B上
printf("%c--> %c\n",X,Z);//将A 上的最后一个盘子移动到C上
hanoi(n-1,Y,X,Z);//将B上的n-1个盘子,借助A,移动到C上
}
}
int main()
{
int n;
printf("请输入盘子的总数:");
scanf("%d",&n);
hanoi(n,'A','B','C');
return 0;
}
我第一次看到这里时,就被震撼到了,一个如此复杂的数学问题竟然可以通过短短二十几行代码来实现,可是里面对于hanio函数的反复调用却让我十分头疼,这是怎么实现的呢?
下面我们以n=3为例来详细的解释一遍,这里我们需要用到一点栈的概念,简单说就是每一次递归之前程序都会将现在的数据储存在栈中(从下往上存储),递归结束后会依次从上往下调出数据,之后程序就会返回到这个数据存储的地方向后继续执行,此时栈中的这一条数据也会消失。
当程序执行到第一个hanoi函数时,显然n!=1,执行else的部分,此时hanoi函数中的变量值为hanoi(3,A,B,C),在执行第8行语句之前,首先要将其存放到栈中。经过下一行的递归后,四个值变为hanoi(2,A,C,B)。
此时栈中的数据为
第一次 hanoi(2,A,C,B) |
初始 hanoi(3,A,B,C) |
再继续第二次运行else后的代码,得到下一组数值hanoi(1,A,B,C)并储存,
再运行时n=1,终于可以执行printf语句啦!
输出 A-->C
这时程序就要开始处理栈中的数据了,从上往下执行,将程序返回原处,赋值后执行下一行,此时函数值为hanoi(2,A,C ,B)
输出 A-->B
继续执行时又遇到了递归程序,这时再将此时的数据储存到栈中,重新开始执行一个hanoi函数,此时hanoi(1,C,A,B)n=1,那么执行if后的语句
输出C-->B
之后返回栈中的第一条数据处,往下执行,这个时候我们发现,第一次递归的hanoi函数到此结束,程序接着处理栈中最底下的数据,hanoi(3,A,B,C)。将程序返回到这里并执行下面的语句
输出 A-->C
下面一条又是递归啦,我们再重复上面的过程,储存数据再重新调用hanoi函数。调用后hanoi(2,B,A,C),依旧不满足n=1,再重复以上过程,得到hanoi(1,B,C,A)
此时栈中的数据为
hanoi(2,B,A,C) |
hanoi(3,A,B,C) |
现在n=1,输出B-->A。
返回栈中第一条数据处,往后执行
输出B-->C
递归得hanoi(1,A,B,C)
再输出A-->C 。
此时栈中的数据为
hanoi(2,B,A,C) |
hanoi(3,A,B,C) |
分别对应的两个数据位置都位于hanoi函数的这个位置
在返回时,都会结束程序,所以整个递归到此结束,让我们统计一下输出的结果
输出 A-->C
输出 A-->B
输出C-->B
输出A-->C
输出B-->A
输出B-->C
输出A-->C
这就是n=3时,整个程序的运行的结果了。
总结:汉诺塔程序本身是一个极其复杂的问题,通过递归,我们可以节省大量的代码,通过一个简短的程序来解决这个问题,但是这其中的逻辑是不可能被简化的,只是由计算机替我们执行了而已,所以彻底的了解整个程序是如何对于hanoi函数一次次的调用,虽然有一定的难度,有点难以理解,我认为是非常有必要的,这可以让我们对递归运算有一个更深刻的了解。以上是n=3的情况,较为简单,大家有能力的可以挑战一下给n赋更大的值,那样递归分析的难度也会同样暴增。
另外,如果想要统计移动次数的话,还可以增加一个全局变量来达到目的。
下面是代码
#include <stdio.h>
int i=0;//增加一个全局变量
void hanoi(int n,char X,char Y,char Z) //n个盘子在A上,借助B,移动到C上
{
i++; //每次调用hanoi函数,i都会+1
if(n==1) //如果A上只有一个盘子,直接移动到C上
printf("%c-->%c\n",X,Z);
else //一旦多余一个,则执行递归程序
{
hanoi(n-1,X,Z,Y); //将A上的n-1个盘子,借助C,移动到B上
printf("%c--> %c\n",X,Z); //将A 上的最后一个盘子移动到C上
hanoi(n-1,Y,X,Z); //将B上的n-1个盘子,借助A,移动到C上
}
}
int main()
{
int n;
printf("请输入盘子的总数:");
scanf("%d",&n);
hanoi(n,'A','B','C');
printf("一共需要移动的次数为%d",i);
return 0;
}
运行截图