一位法国数学家曾编写过一个印度的古老传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。
虽然这只是一个传说,但也给我们提出了一个问题,今天在学python的时候刚好看到一道题目,使用递归解决这个问题,打印出有4个金片的移动过程。我一开始没有注意到题目要求是每次移动一片而且每一个柱子上的金片都要是从小到大排的,还以为很简单,后来发现,实现起来是很麻烦的,令人头疼。
我在网上看了好多对这个问题的解决方法,都是使用递归,但是分析的都很欠缺,导致我一个人考虑了一大早上,直至头痛,大概总结如下:
假设三根石针分别为a,b,c,金片一开始放在a上,我们的目的是把金片按照从小到大的顺序放到c上。
我们从最简单的情况分析,只有一片的话,直接放到c上,这种不必多说了吧。
如果有两片的话,步骤应该是这样的:a->b,a->c,b->c,这里箭头代表每次拿最上面的一片移动到另一根石针上面。可以看到我们如果用c语言写的话应该是:move(2,a,c,b);move(2,a,b,c)move(2,b,a,c);2代表a上有两个金片,(2,a,c,b)代表把a顶上的一个先移动到b上,以此类推;我们每次把移动的步骤打印出来,就可以写出来下面这样的递归:
void move(int n, char a, char b, char c) { if (n == 1) printf("\t%c->%c\n", a, c); else { move(n - 1, a, c, b); printf("\t%c->%c\n", a, c); move(n - 1, b, a, c); } }
但是当我依照这个步骤去想如果a上面放更多的金片的时候,这个步骤因为太麻烦了,我不能推出来,所以也不能验证它的正确性,只知道这是根据递归写出来的。因为它真的很麻烦我不相信那个人可以推出来5以上的步骤,如果可以,我认为他是天才。但是这个问题本身就是这么麻烦,用递归解决的话对某些人来说才是更容易理解的。
下面我推一下当n=3的时候的执行步骤:
因为步骤实在比较多,所以上面的过程图越画越乱,在这里我大概讲述一下,大家看红色的线条,是我一开始的执行路径,刚进这个函数的时候,参数是(3,a,b,c),然后在对函数进行调用,但这一次我们传进来的是(2,a,c,b),因为2还是不等于1,所以再进行调用,这次穿进去的则是(1,a,b,c),这里比较难理解,因为我们可以看到这里的参数顺序不停发生改变,我们可以看我们的函数内部, move(n - 1, a, c, b); move(n - 1, b, a, c); 这两句话都把传进来的参数顺序改变了,至于为什么要这么做,就是我们一开始分析只有两个金片的时候交换的那样,只要你在理解的时候不要被形参(a,b,c)的顺序和我们实际传进来的顺序搞混还是很好理解的。绿色框内的输出就是打印到屏幕上的步骤,褐色的线条是每层递归执行完的返回,当我们返回到第一次进入的递归的内部,又到了move(n - 1, b, a, c)这个函数的递归了,是我图上的橙色线条部分,因为后面的步骤和前面的差不多而且空间有限我就没有再进行分析了,你们可以自己画这样的图帮助分析。
上图是我运行程序的结果,可以看到和我分析的前四个输出是一样的,我在程序中加了计数,算出总共移动了7次;
1 2 void move(int n, char a, char b, char c) 3 { 4 if (n == 1) 5 printf("\t%c->%c\n", a, c); 6 else 7 { 8 move(n - 1, a, c, b); 9 printf("\t%c->%c\n", a, c); 10 move(n - 1, b, a, c); 11 } 12 } 13 14 int main() 15 { 16 int n; 17 printf("请输入要移动的块数:"); 18 scanf_s("%d", &n); 19 move(n, ‘a‘, ‘b‘, ‘c‘); 20 return 0; 21 }
时间: 2024-10-13 06:15:35