首先提供全部代码,如果后面的内容不想看,可直接自取下面代码:
#include <stdio.h>
void hanoi(int n, char from, char aux, char to) {
//如果只有一个盘子,直接从 from 移到 to
//n 的数字由小到大,依次对应着盘子由上到下
if (n == 1) {
printf("Move disk 1 from %c to %c\n", from, to);
return;
}
//多个盘子的情况,将上面的 n-1 个盘子移到辅助棍 aux,给最底下的盘子留下终点棍 to 的空间
hanoi(n - 1, from, to, aux);
//可以将最底下的盘子移到终点棍 to 了
printf("Move disk %d from %c to %c\n", n, from, to);
//剩下的 n-1 个盘子以辅助棍 aux 为起点,借助 from 移到 to
hanoi(n - 1, aux, from, to);
}
int main() {
int n;
printf("Enter the number of disks: ");
scanf("%d", &n);
printf("\n");
hanoi(n, 'A', 'B', 'C');
return 0;
}
汉诺塔这个游戏大家肯定玩过,我这里贴一个图大家应该就能想到玩法:
接下来介绍在C语言中,如何用函数递归解决这个问题。
我们在写递归的时候,先不要试图用脑袋想出一个问题的所有步骤,这在很多时候是不现实的,例如汉诺塔问题,其步骤取决于盘子个数 n ,大小为。很明显 n 如果为4 ,那么就会有15个步骤,那就很难想了。
因此,先不要去想步骤,先要去做的是坚定的相信,自己写的递归函数可以解决自己想解决的问题,这样可以减少自己在写代码时的胡思乱想,避免在代码陷入死循环前,自己的脑袋先陷入死循环。
下面开始解决汉诺塔问题:
为了然问题能够适应更多的情况,用4个盘子进行举例。
三个棍子分别起名为from、aux和to,对应于起点棍,辅助棍和终点棍。
这里面,上面三个盘子明显相对对于最底下的盘子是独立的,因而可以完全不考虑最底下的盘子进行移动,当然最底下的盘子也必须要等到上面三个动完才能动,它最大,其他任何一个盘子都不让他落在上面。而上面三个盘子,很明显要先移动到辅助棍 aux ,最后让最底下的盘子移动到终点棍 to 。忽略中间步骤,进而我们最终要得到的是这样一个画面:
接着,将 aux 上的三个盘子移到 to 上面就大功告成了!!!(忽略上面三个盘子移动的过程)如图:
从上面的步骤来看,将最上面的三个盘子移到 aux ,并从 aux 移到 to ,不就是再搞两次相对简单的汉诺塔问题么,也就是说,我们在自己写的汉诺塔函数中调用两次汉诺塔函数,这个问题不就解决了,只不过这个时候棍子的顺序变成了(from to aux)和(aux from to),接下来开始写代码:
void hanoi(int n, char from, char aux, char to) {
//多个盘子的情况,将上面的 n-1 个盘子移到辅助棍 aux,给最底下的盘子留下终点棍 to 的空间
hanoi(n - 1, from, to, aux);
//可以将最底下的盘子移到终点棍 to 了
printf("Move disk %d from %c to %c\n", n, from, to);
//剩下的 n-1 个盘子以辅助棍 aux 为起点,借助 from 移到 to
hanoi(n - 1, aux, from, to);
}
这里面将函数命名为 hanoi ,并在内部两次调用 hanoi ,中间用 printf 函数模拟移动盘子的过程。正如我在前面提到的,要坚信自己的函数好用,因此我就按照步骤调用它就肯定没有问题,不要去想步骤细节。
当然,上述函数是有问题的,如果 n 为1,也就是有一个盘子,那么 n-1 就是0,再用 hanoi 调用起来(我们假设它是现成好用的)就会有问题,因此我们要在前面加上 (1 == n)的情况:
void hanoi(int n, char from, char aux, char to) {
//如果只有一个盘子,直接从 from 移到 to
//n 的数字由小到大,依次对应着盘子由上到下
if (n == 1) {
printf("Move disk 1 from %c to %c\n", from, to);
return;
}
//多个盘子的情况,将上面的 n-1 个盘子移到辅助棍 aux,给最底下的盘子留下终点棍 to 的空间
hanoi(n - 1, from, to, aux);
//可以将最底下的盘子移到终点棍 to 了
printf("Move disk %d from %c to %c\n", n, from, to);
//剩下的 n-1 个盘子以辅助棍 aux 为起点,借助 from 移到 to
hanoi(n - 1, aux, from, to);
}
这里面我们在(1 == n)时执行盘子1从 from 移动到 to 这个步骤,接下来我们很显然不想再继续调用 hanoi 了,因为已经没有可以移动的了,因此直接 return 出函数。这样 hanoi 函数就写好了,整体代码如下:
#include <stdio.h>
void hanoi(int n, char from, char aux, char to) {
//如果只有一个盘子,直接从 from 移到 to
//n 的数字由小到大,依次对应着盘子由上到下
if (n == 1) {
printf("Move disk 1 from %c to %c\n", from, to);
return;
}
//多个盘子的情况,将上面的 n-1 个盘子移到辅助棍 aux,给最底下的盘子留下终点棍 to 的空间
hanoi(n - 1, from, to, aux);
//可以将最底下的盘子移到终点棍 to 了
printf("Move disk %d from %c to %c\n", n, from, to);
//剩下的 n-1 个盘子以辅助棍 aux 为起点,借助 from 移到 to
hanoi(n - 1, aux, from, to);
}
int main() {
int n;
printf("Enter the number of disks: ");
scanf("%d", &n);
printf("\n");
hanoi(n, 'A', 'B', 'C');
return 0;
}
运行一下:
如果有兴趣的话可以按照上面的步骤实践一下,结果肯定是对的。
我们在遇到问题时,如果这个问题的解决过程中的一个环节是这个问题本身,只不过更简单了,但本质还是问题本身时,就可以用递归实现,实现方法就是直接调用函数本身。接着,我们要考虑的并不是一层一层的嵌套在内部是怎么实现的,而是调用以后的其它步骤,例如在汉诺塔问题中要考虑(1 == n)的情况,并且不要忘记 printf("Move disk %d from %c to %c\n", n, from, to); 这个步骤。
当然,下面这种写法:
void hanoi(int n, char from, char aux, char to) {
if (n == 1)
{
hanoi(1, from, aux, to);
return;
}
//多个盘子的情况,将上面的 n-1 个盘子移到辅助棍 aux,给最底下的盘子留下终点棍 to 的空间
hanoi(n - 1, from, to, aux);
//可以将最底下的盘子移到终点棍 to 了
printf("Move disk %d from %c to %c\n", n, from, to);
//剩下的 n-1 个盘子以辅助棍 aux 为起点,借助 from 移到 to
hanoi(n - 1, aux, from, to);
}
这种写法是不可以的,我们虽然坚信自己函数好用,但 hanoi(1, from, aux, to); 这种情况1是固定值是不变的,(n == 1)这个条件也是固定不变的还正好对应了那个1,从 if 下来以后明显会导致死循环,要特殊注意。在没有死循环的情况下,还是可以放心调用自身的,只要注意把东西写全就行,不要想太多。