目录
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
今天我们就运用c语言来实现《汉诺塔游戏》的实现。
我们主要运用的方法是C语言中的递归操作,该内容着实有点抽象,而且有许多细节需要注重,并且注意力要保持高度集中,第一次可能会听不懂,但是坚持下来多动动手,就可以有很好的理解。
以下为汉诺塔函数实现的内容:
- void hanoi(int n, char A, char B, char C)
- {
- if (n == 1)
- {
- move(A, C);
- }
- else
- {
- hanoi(n - 1, A, C, B);
- move(A, C);
- hanoi(n - 1, B, A, C);
- }
- }
我们给出此函数当然还要着重讲解为什么我们要这么来实现,我们先通过画图来举出简单的例子。
若我们的塔的个数仅仅为1,那我们想要将塔挪动到C柱子则我们只需要一步就可以轻松挪动到C。
但如果我们的塔数为2呢?
若是如此,我们则需要现将上面小的先挪动到B柱,然后再将下面大的挪动到C柱子,再在将B柱中的小的挪动到C柱,则需要3步
通过上述,我们可以初步找到规律
我们其实可以将除了最下面大的那一块以上的全部块数,可以先将它们挪动到B柱,然后再将最下面的大块挪到到C柱,再将剩下的挪动到C柱。
若是我们将塔数改为3呢?
按照我们上述发现的规律,我们是不是也可以现将上面两块想办法挪动到B柱,
再将最下面大的那一块挪动到C柱子,再将剩下的两块想办法从B柱挪动到C柱。
即
再
规律确实是如此,但我们要怎样对上述的代码进行解释呢?
首先我们要清楚的是,我们是有三根柱子,其实对这三根柱子,我们在上述实现或者线下我们自己玩时,可以分别给上述三根柱子起名字。
A柱——起点柱子
B柱——辅助柱子
C柱——终点柱子
那我们在写函数时,就可以写成:
void hanoi(int n, char A, char B, char C)
n表示的就是塔的个数,
A表示的就是起点柱子
B表示的就是辅助柱子
C表示的就是终点柱子
那么该函数的意思就是:
就用B柱子这个辅助柱子将n层塔从A柱子挪动到B柱子。
接下来我们进行判断:
如果仅为一层塔,那我们直接将A柱子的塔挪动到C柱子。
即move(A,C);
- if (n == 1)
- {
- move(A, C);
- }
当然我们肯定想玩高级点的,比如三层塔。
那么就会有一个else语句:
其中根据以上的规律,就是将除最下面的大块其余的层挪动到B柱子,则
我们可以借用C柱子将A柱子的n-1塔数挪动到B柱子。
则我们可以利用第一个递归:
hanoi(n-1, A, C, B);
在我们将n-1的塔数挪动完后,我们就可以将剩下的最大一块直接挪动到C柱子。
即
move(A, C);
那么剩下我们就可以将在B柱子的n-1塔数借用A柱子,挪动到C柱子。
即
hanoi(n-1, B, A, C);
如此一来该hanoi函数的全部内容已全部讲解清楚。
接下来我们就进入代码,一步一步看看该函数是怎么实现挪动的。
- void hanoi(int n, char A, char B, char C)
- {
- if (n == 1)
- {
- move(A, C);
- }
- else
- {
- hanoi(n - 1, A, C, B);
- move(A, C);
- hanoi(n - 1, B, A, C);
- }
- }
我们现在就一步一步来看看函数递归是怎么实现的,以下内容我会讲解的特别细致,需要我们大家高度集中。
首先我们将塔的个数即n设置为3。
先进行判断,此时n == 3不满足语句为假,则跳转到else语句。
接下来我们就进行本次函数的第一次递归:
此时我们进行了递归操作,这次我们我们可以再次重新写个函数方便大家理解:
即:
- //第一次递归,我们要注意参数的变化
- void hanoi(int n, char A, char C, char B)
- {
- if (n == 1)
- {
- move(A, B);
- }
- else
- {
- hanoi(n - 1, A, B, C);
- move(A, B);
- hanoi(n - 1, C, A, B);
- }
- }
此时我们在递归中的函数又要进行一次判断,要注意的是递归中的n此时为2,因为递归中为n-1.
if条件不成立,条件为假,跳转到else语句。
则又会进行一次递归操作:
- //第一次递归中的第一次递归:注意参数变化,参数变为原函数
- void hanoi(int n, char A, char B, char C)
- {
- if (n == 1)
- {
- move(A, C);
- }
- else
- {
- hanoi(n - 1, A, C, B);
- move(A, C);
- hanoi(n - 1, B, A, C);
- }
- }
此时第一次递归中的第一次递归里的n为1,则在进行if语句的判断时,条件为真,进入语句:
此时是将A柱子的最上面的塔挪动到C柱子:
第一次递归中的第一次递归结束,返回第一次递归的函数,并执行下一条语句:
这一条语句则为将A柱子中中间大的塔,挪动到B柱子,即:
接下来,进行第一次递归的第二次递归操作:
即:
- //第一次递归里的第二次递归操作
- void hanoi(int n, char C, char A, char B)
- {
- if (n == 1)
- {
- move(C, B);
- }
- else
- {
- hanoi(n - 1, C, B, A);
- move(C, B);
- hanoi(n - 1, A, C, B);
- }
- }
此时第一次递归中的第二次递归操作中的n为1,当我们进行if语句的判断时,条件为真,则进入语句:
move(C,B)意思就是将C柱子最小的塔挪动到B柱子,与中间大的塔重合:
如此第一次递归的第二次递归结束,同时也标志着第一次递归结束。
此时我们就会执行以下语句:
move(A,C)在此时就是将最大的那一块塔挪动到C柱子,即
接着进行下一条语句,此时就会进行第二次递归操作:
则:
- //第二次递归操作
- void hanoi(int n, char B, char A, char C)
- {
- if (n == 1)
- {
- move(B, C);
- }
- else
- {
- hanoi(n - 1, B, C, A);
- move(B, C);
- hanoi(n - 1, A, B, C);
- }
- }
此时在第二次递归操作中的n为2,进行if语句判断,条件为假,跳转到else语句中:
此时又会进行依次递归操作,此时为
第二次递归里的第一次递归操作:
- //第二次递归里的第一次递归操作
- void hanoi(int n, char B, char C, char A)
- {
- if (n == 1)
- {
- move(B, A);
- }
- else
- {
- hanoi(n - 1, B, A, C);
- move(B, A);
- hanoi(n - 1, C, B, A);
- }
- }
此时在第二次递归操作里的第一次递归中的n为1
则在进行if语句的判断时,条件为真,进入if语句中。
move(B,A)则是将B柱子上最小的塔挪动到A柱子上,即:
此时第二次递归里的第一次递归操作结束,返回第二次递归的函数中,同时进行执行以下语句:
move(B,C)是将B柱子中剩下的中间大的塔挪动到C柱子,与最大的塔重合,即:
接下来我们又会进行一次递归,为第二次递归中里的第二次递归操作,为:
- //第二次递归里的第二次递归操作
- void hanoi(int n, char A, char B, char C)
- {
- if (n == 1)
- {
- move(A, C);
- }
- else
- {
- hanoi(n - 1, A, C, B);
- move(A, C);
- hanoi(n - 1, B, A, C);
- }
- }
在第二次递归里的第二次递归操作中,n此时为1,进入if语句中判断,条件为真,则进入if语句中:
执行语句move(A, C)
将A中剩下的最小的塔挪动到C柱子,与之重合,则正好完成将三层塔从A柱子挪动到C柱子。
此时第二次递归里的第二次递归操作结束。
同时整个程序结束,已经完成全部过程,如图:
对程序的完整实现,我们想要打印过程,并得出最后走了几步的过程实现,我们不妨可以这样编写:
- int main()
- {
- int n = 0;
- char A = 'a';
- char B = 'b';
- char C = 'c';
- scanf("%d", &n);
- hanoi(n, A, B, C);
- printf("需要%d步\n", count);
- return 0;
- }
我们同时也要注意,我们在hanoi函数里存在move函数,则在move函数里我们可以这样实现:
- static int count = 0;
-
- void move(char A, char C)
- {
- printf("%c -> %c\n",A,C);
- count++;
- }
此时运用static可以修饰静态全局变量,将count的值固定,则如此进行代码演示:
如此一来代码全部已全部实现和完善。
以上就是我们对《汉诺塔游戏》的实现,我们需要对函数的讲解和实现进行理解,接下来尝试动手调试调试,在纸上画画图,将自己给彻底弄明白,第一次听不懂没关系,我们可以再进行多次的练习,我们要注意的是,我们一定要多动动手。
记住:
“坐而言不如起而行”
Action speak louder than words.
光说不练,假把式!
想要源代码可以访问我的Gitee仓库:
hanoi_practice_1/hanoi_practice_1/test.c · 无双/test_for_code_with_X1 - Gitee.com