目录
1.前言
学完函数后,不知各位是否和我一样有种意犹未尽的感觉。如果有,或许是因为它——函数递归,这个学习函数怎么也绕不开的章节,这个让程序员开心,让计算机难受的算法.......那么事不宜迟,赶紧开启下一程,一了我们的夙愿。
2.何为递归
void test()
{
printf("hello world!\n");
test();//test函数自己调用自己
}
int main()
{
test();
return 0;
}
运行起来看看:
可为啥好端端的函数递归最后变成死循环了呢?说明函数递归并不是简单的函数自己调用自己,还必须要有限制条件
3.递归的限制条件
要想写出正确的函数递归,有两个必要不充分条件: ① 递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。 ②每次递归调用之后越来越接近这个限制条件 即包含这两个限制条件的函数递归不一定正确,但是没有这两个限制条件的递归就一定是错的。
我们将上面的函数递归加上这两个限制条件 :
void test(int n)
{
if (n > 0)//限制条件
{//每一次递归n的值都更加接近0直到不满足该条件
printf("hello world!\n");
test(n-1);//每一次递归都使n减1
}
}
int main()
{
int n = 3;//打印3次 hello world!
test(n);
return 0;
}
4.递归的思想
5.递归举例
递归求解核心思想: 一个问题直接求解时不好求解,如果可以将其划分成其子问题,并且子问题和原问题有相同的解法时,就可以使用递归的方式解决 。
5.1小乐乐走台阶
牛客网中有这样一道题目:
我们把自己想象成小乐乐,征服整个台阶就是我们的最终目标,那么有多少种方法来征服它呢?当n=1或者n=2时,目标过于简单,掐指一算,我们有一种和两种方法征服。当n过大时我们无法一步登顶,那就把它拆成一个的战略小目标和余下的大目标。此时我们不妨从n=3入手,第一个战略目标:先走一步看看,这样我们我有上一阶台阶和上两阶台阶两种选择。剩下的战略目标就有走完一阶或走完两阶两种,这不就到了我们掐指一算的时候吗?所以征服3阶台阶的方法数=征服两阶台 方法数+征服一阶台阶的方法数。
那么这样一来n=5时不就是n=4时的加上n=3时的吗。那么n时的就是n-1时的和n-2时的方法之和 。那么n-1时的就可以拆成n-2时的和n-3时的方法之和.......以此类推,直到拆成我们可以掐指一算的n=1和n=2时即可。
我们把大问题n拆成子问题n-1和n-2,并且子问题和大问题一样可以拆成更小的子问题,这不就是递归的核心思想吗?
那么代码来:
#include <stdio.h>
//int count = 0;
int step(int n)
{
if (n <= 2)//当我们掐指一算时递归停止
{
if (1 == n)
return 1;
if (2 == n)
return 2;
}
/*if (n == 5)
count++;*/
if (n > 2)
{//将n时的拆成n-1时的和n-2时的方法之和
return (step(n - 1) + step(n - 2));
//函数递归时所传递的参数越来越接近限制条件
}
return 0;
}
int main()
{
int n = 0;
scanf("%d", &n);
if (n > 0)
step(n);
else
printf("输入错误\n");
printf("%d\n", step(n));
//printf("第五项计算了%d次", count);
return 0;
}
5.2斐波那契数列
提到斐波那契数列相信大家都不会很陌生,它的规律是从第三项开始,该项上的数等于前两项之和。 1,1,2,3,5,8,13..... 那么我们来写一个代码实现求解任一项斐波那契数列的值。
思路:谈及它的规律——该项上的数等于前两项之和,即求解第n项斐波那契数列时,将其拆成第n-1项和第n-2项之和。这不就求解我们上一题时所遇到的情况吗?好家伙,竟然直接撞枪口上了,那也就不装了,直接开干:
int count=0;
int F(int n)
{
/*if (5 == n)
count++;*/
if (n <= 2)
return 1;
else
return F(n - 1) + F(n - 2);
//每一次递归所传递的参数都更加接近限制条件
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = F(n);
printf("第%d项为%d\n",n, ret);
//printf("第五项共循环了%d次\n", count);
return 0;
}
6.递归与迭代
6.1递归的弊端
递归是一种很好的编程技巧,因为只需要寥寥几行代码,就可以较为轻易地解决我们使用循环解决感到困难的问题。但是递归也有它致命的弊端,那就是代码的运行效率并不高。究其主要原因,就是因为有些数据没有很好地利用起来,反而计算了很多次。
以上面的斐波那契数列为例,当所求的n比较大时,第五项的值就会被重复计算多次。我们取消计算该次数的注释,运行起来看看:
隔着屏幕都能感觉到计算器的温度。所以当我们的需要递归地比较深时,运行效率会以肉眼可见的速度下降。这时我们就果断放弃使用递归,改用迭代的方式求解。
6.2斐波那契数列(迭代篇 )
所谓迭代,就是为了逼近目标结果,重复反馈过程的活动。循环语句就是一种迭代。
那该怎么用循环语句实现求解对应项的数的代码呢?我们就从它的底层逻辑出发。既然从第三项开始,每一项等于前两项之和,那么我们就设置三个变量a,b,ret用来表示相邻两项的值,以及他们的和。每进行一次,b的值赋给a,ret的值赋给b,新一轮a与b的和赋给ret,以此类推。
int main()
{
int n = 0;
int ret = 0;
int a = 1;
int b = 1;
scanf("%d", &n);
if (1 == n || 2 == n)
ret = 1;
else
{
while (n-2)//确保n>=3
{
ret = a + b;
a = b;
b = ret;
n--;
}
}
printf("%d", ret);
return 0;
}
这样计算n较大时代码运行起来的效率就明显提高了!
7.总结
函数递归第一个非常实用的技巧,合理地运用会使代码更加简洁的,思考问题多一个方向。但不应该“迷信递归”,当递归层次较深,应该果断放弃,进而选择迭代等运行效率更高的算法。