递归概念
在C语言中,通俗一点来讲,递归就是函数自己调用自己。
递归中的递就是递推,归就是回归。
思想:
把一个大型复杂问题转换为一个与原问题相似但规模较小的子问题求解,所以递归的思考⽅式就是把⼤事化⼩的过程。
使用条件:
1.存在限制条件
2.每次递归逐渐接近限制限制条件
正所谓光学不练假把式,下面介绍递归思想在实战(以函数为主)上的具体应用
递归在函数中使用示例
举例1:求n的阶乘
计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。
我们知道n的阶乘的公式: n! = n ∗ (n − 1)!
注意到
递归的思路就是把⼀个较⼤的问题,转换为⼀个与原问题相似,但规模较⼩的问题来求解的。
所以
n! —> n*(n-1)!
(n-1)! —> (n-1)*(n-2)!
…
2!—>2 *1!
int Fact(int n)
{
if(n<=0)
return 1;
else
return Fact(n-1);
}
下面以n=5时为示例 分析代码底层运行逻辑
举例2:求第n个斐波那契数
斐波那契数的问题通过是使⽤递归的形式描述的
int Fib(int n)
{
if(n<=2)
return 1;
else
return Fib(n-1)+Fib(n-2);
}
递归与迭代
在C语⾔中每⼀次函数调⽤,都要需要为本次函数调⽤在栈区申请⼀块内存空间来保存函数调⽤期间
的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧。
函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次递归
函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。
所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢
出(stack overflow)的问题
所以如果不想使⽤递归就得想其他的办法,通常就是迭代的⽅式(通常就是循环的⽅式)。
对于示例一:
用迭代方式书写代码
int Fact(int n)
{
int i = 0;
int ret = 1;
for(i=1; i<=n; i++)
{
ret *= i;
}
return ret;
}
对于示例二:
用迭代方式书写代码
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while(n>2)
{
c = a+b;
a = b;
b = c;
n--;
}
return c;
}
注意到
对于示例二用递归思想解决的代码
当我们n输⼊为50的时候,需要很长时间才能算出结果,这个计算所花费的时间,是我们很难接受的,
这也说明递归的写法是非常低效的。
递归程序会不断的展开,在展开的过程中,我们很容易就能发现,在递归的过程中会有重复计
算,⽽且递归层次越深,冗余计算就会越多。
下面进行代码测试:
#include <stdio.h>
int count = 0;
int Fib(int n)
{
if(n == 3)
count++;//统计第3个斐波那契数被计算的次数
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;
}
运行结果
在计算第40个斐波那契数的时候,使⽤递归⽅式,第3个斐波那契数就被重复计算了39088169次,这些计算是非常冗余的。所以斐波那契数的计算,使⽤递归是⾮常不明智的,我们就需要用迭代的⽅式解决。