目录
一、递归的概念
- 递归是我们学习c语言时绕不开的话题,那么什么是递归呢?
- 递归其实是一种解决问题的办法,可以理解为函数调用函数自己
- 函数递归是指在函数内部直接或间接调用自身的一种编程技巧。递归通常用于解决可以分解为相同子问题的问题,例如阶乘、斐波那契数列、树的遍历等。
现在给大家一个史上最简单的c语言递归代码:
#include <stdio.h>
int main()
{
printf("hehe\n");
main();//main函数中有调用了main函数
return 0;
}
可以看到main函数中又调用了main函数,所以一直在打印"hehe"。
这个是一个简单的递归程序,只不过上面的递归只是为了展示递归的基本形式(自己调用自己),不是为了解决问题,代码最终也会陷入死递归,导致栈溢出(Stack overflow)
1.1 递归的基本条件(限制条件)
- 基准条件(Base Case):递归必须有一个明确的终止条件,否则会陷入无限循环。
- 递归条件(Recursive Case):函数需要调用自身,且每次调用应逐步接近基准条件。
1.2 递归的思想
- 把一个大型复杂的问题层层转换成一个与原问题相似,但规模更小的字问题来解决 ;简而言之,就是把大事化小的过程。
- 递归的递就是递推的意思,归就是回归的意思,这一句等会实践后大家就可以非常清楚的理解。
二、递归举例
2.1 举例1:求n的阶乘
题目:计算n的阶乘(不考虑溢出),n的阶乘是1~n的数字累积相乘。
5!=5*4*3*2*1
4!=4*3*2*1
所以5! = 5*4!
从这个问题来看,我们该如何实现大事化小的思想呢?如果让我们计算1000的阶乘就傻乎乎的1000*999******1吗。
n的阶乘和n-1的阶乘是相似的问题,但是规模少了一个n。有一种特殊的情况是:当 n==0 的时候,n的阶乘就是1,而其余的阶乘都是可以通过上面的公式计算。
这样我们就能写出n的阶乘公式如下 :
那我们就可以写出函数fun求n的阶乘,假设fun(n)就是求n的阶乘,那么fun(n-1)就是求n-1的阶乘,函数如下
int Fun(int n)
{
if (n == 0)
return 1;
else
return n * Fun(n - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fun(n);
printf("%d\n", ret);
return 0;
}
运行结果(这里不考虑n太大的情况,n太大会导致溢出
流程图如下
三、递归与迭代
迭代:重复做一件事,每次都可能微调
- 递归:简介,根据公式写代码,但会占用很多空间,浪费内存
- 迭代:写代码比较难,但是效率高,空间浪费少
按照举例上面的代码来说,其实递归这个方法确实方便,看到推导的公式很容易被写成递归的形式,但递归也有自己的弊端,当你不想用递归时就得想其他的方法,通常会使用迭代的方式(循环)
比如:计算n的阶乘,也可以产生1~n的数字累计乘在一起。
int Fun(int n)
{
int i = 0;
int ret = 1;
for (i = 1;i <= n;i++)
{
ret *= 1;
}
return ret;
上面这个代码就可以很好的完成任务,并且效率是比递归的方式更好的。
四、斐波那契函数
我们也能举出更加极端的痢疾,就像计算n个斐波那契函数,适不适合使用递归求解的,但是斐波那契数的问题通过使用递归形式描述的,如下
看到公式,我们就很难不想到递归的形式,代码如下
int Fib(int n)
{
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
测试代码:
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
return 0;
}
当我们输入50的时候,我们要等好久好久才有结果,这个计算非常花时间,是我们很难接受,特别是我们参加竞赛的时候很多题都有运算速度限制,这说明这题我们用递归是非常没有效率的,那是为什么呢?
大家可以看到,在跑代码的时候会重复很多,这大大降低了我们的效率,而且递归的层次越深,多余的计算就会越来越多,我们可以看看
#include<stdio.h>
int count = 0;
int Fib(int n)
{
if (n == 3)
count++;
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
printf("\ncount = %d\n", count);
return 0;
}
输出结果
大家能看到只是30,就要算317811次,这些计算是非常多余的,那么显而易见,这个使用递归的方式非常不明智,那我们可以使用迭代的方式
我们知道斐波那契数的前两个数都是1,然后前两个数相加就是第三个数,那么我们从前往后,从小到大计算就可以了。
这样就有下面的代码:
迭代的方法实现,效率就高出很多了。