刚学函数递归的时候,肯定会觉得函数递归又难又枯燥,那么怎么才可以学好函数递归呢?其中一个好的方法不乏多多练习,多多认识函数递归地题型。那么接下来我们来认识一个有趣的函数递归问题:汉诺塔问题。
首先我们来了解一下函数递归的优点和弊端。使用函数递归可以使得我们的问题更加直观,更加清晰,可读性更好,更逼近数学公式,我们可以通过函数递归的形式将抽象的逻辑问题转化为数学问题加以解决,对于数值计算领域的问题都可以运动函数递归解决。但是函数递归的弊端就在于:递归函数在每次递归调用时都需要进行参数传递,现场保护等操作,增加了函数调用的时空开销,从而导致递归程序的时空效率比较低,所以一般我们建议为了提高程序的执行效率尽量用迭代的形式来代替函数递归。
但是最重要的是我们一定要了解函数递归并且可以熟练使用函数递归,这样不仅可以为我们解决问题提供一个新的思路,而且有的问题之可以利用函数递归才可以进行逻辑上的解决。比如我们接下来要说的汉诺塔问题。
只有用递归才可以解决的问题——汉诺塔问题
首先让我们来了解一下汉诺塔问题的题目要求:
相传在古印度圣庙中,有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。
在了解了题目要求之后,我们首先做的就是分析题目,并试着从几个简单的操作之中找到一定的规律。我们先后先来看只有一个的情况
只需要移动一次即可达到目的。
有两个方快的情况:
需要移动三次才可以达到预想的效果,同样的要想找到规律至少枚举三到四组样例。
有三个方块的情况:
如图移动的方法,一共需要移动七次才可以达到预想的目的。
有四种方块的情况:
由上述枚举方法可以得出如下表格:
有一个方块 | 有两个方块 | 有三个方块 | 有四个方块 |
1 | 3 | 7 | 15 |
既然已经通过枚举知道了前几个方块的数量,那么接下来就可以分为两种方法进行求解了。
1.数学公式求解法。
如果我们数学比较好的话可以凭借我们自身对数字的敏感程度,寻找前几个数字之间的关系,我们可以得出一个公式:2^n-1。n为方块的数量,那么2^n-1就是需要移动的次数。既然得出了这个结论之后我们就可以利用这个数学公式编写程序:
#include<stdio.h>
#include<math.h>
int main()
{
int n = 0;
scanf("%d", &n);
printf("需要移动%d次\n", (int)pow(2, n) - 1);
return 0;
}
因此我们上述的程序就可以得出正确的结果,但是,这并不是我们这次博客要讲的重点,重点是方法二——递归程序的编写。
2.递归程序解决汉诺塔
同样的道理,我们要重新回到题目当中。我们可以发现,在最大的方块想要到达柱子C时,就必须将全部的方块移至B。(A和C均上不能有小方块挡住大方块的去路)那么我们是不是可以先将最大的方块忽略呢?
将我们的题目条件也可以从A移到C改为从A移到B,因为B和C柱没有什么本质上的区别,所以就是上一种情况的移法。
之后我们就可以将最大的方块一步移到C,之后再将最大的方块忽略。
同样的我们下一目标就是将B中方块移到C同样是我们符合我们上一步的操作。那么我们就可以得出一个规律:han(n)=2*han(n-1)-1。这就是我们通过分析得出的递归公式。我们通过这个公式写出来的程序就是:
#include<stdio.h>
//汉诺塔递归公式
int han(int n)
{
if (n == 1)
{
return 1;
}
else
{
return 2 * han(n - 1) + 1;
}
}
int main()
{
int n = 0;
do
{
printf("请输入一个大于0的数字:");
scanf("%d", &n);
if (n < 0)
{
n = 0;
}
} while (!n);
printf("%d", han(n));
return 0;
}
通过上面的分析,我们的汉诺塔问题也就解决了。
当我们遇到比较难的递归问题的时候千万不要慌张,可以试着通过枚举的方法列出几个式子,之后寻找其中的规律。或者将本步骤的变量试着分解看看能不能找到和上一步骤变量之间的关系。这样递归问题就会迎刃而解了。
那么本次博客到此结束,祝你天天开心。