汉诺塔的递归求解分析学完函数,就马上出了道经典的汉诺塔来,书里说是把递归提前拿来研究学习了,这题目实在是把我弄晕了。
几天都在时时想这个题目。
递归是数学归纳法的逆过程。
递归函数是直接或通过另一个函数间接调用自己的函数。
C语言的特点就是允许函数的递归调用。
如果一个问题要用递归解决,得符合以下的条件:1,该问题要能转换成一个新问题,而新问题的解决方法要和原来的问题相同,只是复杂度有所减少而已。
既是要有一定的规律。
如求n!。
2、这个问题当简单到一定程度就可以解决,而不用再继续简化。
(即需要一个结束递归的条件。
否则无限的递归下去,最终会导致系统资源枯竭系统崩溃)。
3、问题用其他方法解决非常困难或不如用递归解决来的简单,(所有递归能解决的问题都能用迭代{非递归}来解决)这个条件是非必要的,但人总需要简单。
?要用递归解决问题,我们必须分析下列问题:1、递归的参数,用递归解决的问题通常都比较复杂,规模比较大,要找出决定递归复杂度,规模的参数,比如n!,决定的递归复杂度、规模的就是n。
2、找出递归结束的标志,没有递归结束的条件,将无限循环。
造成的后果是严重的。
3、找出递归的通式,才可以进一步简化问题。
(通常这是比较困难的)(比如:n!的通式就是n*(n-1)!,而且是可以不断简化直到到达结束递归的边界值)???一般的格式是:?if 结束条件1表达式1(赋予边界值1)else if 结束条件2表达式2(赋予边界值2)...else递归的解决问题的通式。
??汉诺塔的问题;这个问题对于我这个初学者来说,确实棘手,对于执行的步骤很不理解,虽然递归不用去了解执行的步骤的。
但是,不用去了解不等同于不了解。
一个庙里有三个柱子,第一个有64个盘子,从上往下盘子越来越大。
要求庙里的老和尚把这64个盘子全部移动到第三个柱子上。
移动的时候始终只能小盘子压着大盘子。
1、此时老和尚(后面我们叫他第一个和尚)觉得很难,所以他想:要是有一个人能把前63个盘子先移动到第二个柱子上,我再把最后一个盘子直接移动到第三个柱子,再让那个人把刚才的前63个盘子从第二个柱子上移动到第三个柱子上,我的任务就完成了,简单。
所以他找了比他年轻的和尚(后面我们叫他第二个和尚)(呵呵,倚老卖老),命令:①你把前63个盘子移动到第二柱子上②在我自己把第64个盘子一道第三个柱子上后③你把前63个盘子移动到第三柱子上2、第二个和尚接了任务,也觉得很难,所以他也和第一个和尚一样想:要是有一个人能把前62个盘子先移动到第三个柱子上,我再把最后一个盘子直接移动到第二个柱子,再让那个人把刚才的前62个盘子从第三个柱子上移动到第三个柱子上,我的任务就完成了,简单。
所以他也找了比他年轻的和尚(后面我们叫他第三和尚)(呵呵,又倚老卖老),命令:①你把前62个盘子移动到第三柱子上②在我自己把第63个盘子一道第二个柱子上后③你把前62个盘子移动到第二柱子上3、第三个和尚接了任务,又把移动前61个盘子的任务依葫芦话瓢的交给了第四个和尚,等等递推下去,直到把任务交给了第64个和尚为止(估计第64个和尚很郁闷,没机会也命令下别人,因为到他这里盘子已经只有一个了)。
4、到此任务下交完成,到各司其职完成的时候了。
?完成回推了:第64个和尚移动第1个盘子,把它移开,然后第63个和尚移动他给自己分??配的第2个盘子。
第64个和尚再把第1个盘子移动到第2个盘子上。
到这里第64个和尚的任务完成,第63个和尚完成了第62个和尚交给他的任务的第一步。
从上面可以看出,只有第64个和尚的任务完成了,第63个和尚的任务才能完成,只有第2个和尚—第64个和尚的任务完成后,第1个和尚的任务才能完成。
这是一个典型的递归问题。
?现在我们以有3个盘子来分析:?第1个和尚命令:㈠第2个和尚你先把第一柱子前2个盘子移动到第二柱子。
(借助第三个柱子)㈡第1个和尚我自己把第一柱子最后的盘子移动到第三柱子。
㈢第2个和尚你把前2个盘子从第二柱子移动到第三柱子。
很显然,第㈡步很容易实现(哎,人总是自私地,把简单留给自己,困难的给别人)其中第㈠步。
第2个和尚他有2个盘子,他就命令:①第3个和尚你把第一柱子第1个盘子移动到第三柱子。
(借助第二柱子)②第2个和尚我自己把第一柱子第2个盘子移动到第二柱子上。
③第3个和尚你把第1个盘子从第三柱子移动到第二柱子。
同样,第步很容易实现,但第3个和尚他只需要移动1个盘子,所以他也不用在下派任务了。
(注意:这就是停止递归的条件,也叫边界值)第㈢步可以分解为,第2个和尚还是有2个盘子,命令:①第3个和尚你把第二柱子上的第1个盘子移动到第一柱子。
②第2个和尚我把第2个盘子从第二柱子移动到第三柱子。
③第3个和尚你把第一柱子上的盘子移动到第三柱子。
分析组合起来就是:1-->3 1-->2 3-->2 1-->3 2-->1 2-->3 1-->3共需要七步。
如果是4个盘子,则第一个和尚的命令中第1步和第3步各有3个盘子,所以各需要7步,共14步,再加上第1个和尚的1步,所以4个盘子总共需要移动7+1+7=15步,同样,5个盘子需要15+1+15=31步,6个盘子需要31+1+31=64步……由此可以知道,移动n 个盘子需要(2的n次方)--1步。
?从上面整体综合分析可知把n个盘子从1座(相当第一柱子)移到3座(相当第三柱子):(1)把1座上(n-1)个盘子借助3座移到2座。
(2)把1座上第n个盘子移动3座。
(3)把2座上(n-1)个盘子借助1座移动3座。
?下面用hanoi(n,a,b,c)表示把1座n个盘子借助2座移动到3座。
很明显,(1)步上是 hanoi(n-1,1,3,2)(2)步上是hanoi(n-1,2,1,3)????下面便是代码:?#include<stdio.h>static long int m=1;/*用来计算移动步骤步数,静态变量才能不断叠加*/void hanoi(int n,int a,int b,int c){if(n==1){printf("(%d) %d-->%d/n",m,a,c);m++;}else{hanoi(n-1,a,c,b);printf("(%d) %d-->%d/n",m,a,c);/*只有当上一句得到结果,它才会执行*/m++;hanoi(n-1,b,a,c);}}main(){int d;printf("Enter the hanoi shu:");scanf("%d",&d);hanoi(d,1,2,3);return 0;}?执行:Enter the hanoi shu: 3(1)1-->3(2)1-->2(3)3-->2(4)1-->3(5)2-->1(6)2-->3(7)1-->3这里可能有个疑问:移动n-1个盘子确实是上面的步骤,但移动n-2的时候就不一样了啊?hanoi(n-1,a,c,b);printf("(%d) %d-->%d/n",m,a,c);/*只有当上一句得到结果,它才会执行*/m++;hanoi(n-1,b,a,c);?其实每一次调用hanoi(n,a,b,c)时a,b,c的值都不同。
如hanoi(n,1,2,3)时a=1,b=2,c=3.hanoi(n-1,a,c,b);{a=1,b=2,c=3}就是hanoi(n-1,1,3,2);所以调用的就是1-->2再次调用成了hanoi(n-2,a,c,b){a=1,b=3,c=2}就是hanoi(n-2,1,2,3);所以调用的就是,1-->3;每一次都能不断自动改变。
?Hanoi塔问题中函数调用时系统所做工作?一个函数在运行期调用另一个函数时,在运行被调用函数之前,系统先完成3件事:①将所有的实参、返回地址等信息传递给被调用函数保存。
②为被调用函数的局部变量分配存储区;③将控制转移到被调用函数的入口。
?从被调用函数返回调用函数前,系统也应完成3件事:①保存被调用函数的结果;②释放被调用函数的数据区;③依照被调用函数保存的返回地址将控制转移到调用函数。
当有多个函数构成嵌套调用时,按照“后调用先返回”的原则(LIFO),上述函数之间的信息传递和控制转移必须通过“栈”来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就为其在栈顶分配一个存储区,每当从一个函数退出时,就释放其存储区,因此当前运行函数的数据区必在栈顶。
堆栈特点:LIFO,除非转移或中断,堆栈内容的存或取表现出线性表列的性质。
正是如此,程序不要求跟踪当前进入堆栈的真实单元,而只要用一个具有自动递增或自动递减功能的堆栈计数器,便可正确指出最后一次信息在堆栈中存放的地址。
一个递归函数的运行过程类型于多个函数的嵌套调用,只是调用函数和被调用函数是同一个函数。
因此,和每次调用相关的一个重要的概念是递归函数运行的“层次”。
假设调用该递归函数的主函数为第0层,则从主函数调用递归函数为进入第1层;从第i层递归调用本函数为进入下一层,即i+1层。
反之,退出第i层递归应返回至上一层,即i-1层。
为了保证递归函数正确执行,系统需设立一个“递归工作栈”,作为整个递归函数运行期间使用的数据存储区。
每一层递归所需信息构成一个“工作记录”,其中包括所有实参、所有局部变量以及上一层的返回地址。
每进入一层递归,就产生一个新的工作记录压入栈顶。
每退出一层递归,就从栈顶弹出一个工作记录,则当前执行层的工作记录必是递归工作栈栈顶的工作记录,称这个记录为“活动记录”,并称指示活动记录的栈顶指针为“当前环境指针”。
????下面为图例,以三个盘子为例:??Hanoi塔问题中函数调用时系统所做工作处参考文章:《递归处理汉诺塔问题》。