1.什么是递归
递归是一种解决问题的方法,在C语言中,递归就是函数调用自己
#include <stdio.h>
int main()
{
printf("hehe\n");
main();//main函数中⼜调⽤了main函数
return 0;
}
上面是一个简单的递归程序,不是为了解决问题,最终代码会死递归,导致栈溢出
递归的思想
把一个大型复杂问题层层转化为一个与原问题相似,但规模较小的子问题进行求解,直到子问题不能被拆分,递归结束,总之就是大事化小的过程
递归中的递是递推的意思,归是回归的意思
2.递归的限制条件
递归在书写的时候,有2个必要条件
- 递归存在条件,当满足这个限制条件时,递归就不再继续
- 每次递归调用后,都越来越接近这个限制条件
3.递归举例
3.1举例1:求n的阶乘,不考虑溢出
3.1.1分析和实现
阶乘公式:n! =n*(n-1)!
例:
5! = 5*4*3*2*1
4! = 4*3*2*1
所以:5! = 5*4!
n! →n*(n-1)! 这样的思路就是把较大的问题转为较小的问题解决,直到n是1或0,不再拆解
分析
根据以下公式,实现代码
int Fact(int n)
{
if (n <= 0)
return 1;
else
return n * Fact(n - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret=Fact(n);
printf("%d\n", ret);
return 0;
}
3.1.2画图推导
3.2举例2:顺序打印一个整数的每一位
输入一个整数m,按照顺序打印整数的每一位
输入:1234 输出;1 2 3 4
3.2.1
分析和实现
如果n是一位数,n的每一位就是n自己
n超过一位数的话,就得进行拆分
- 1234%10得到4,1234/10得到123
- 123%10得到3,123/10得到12
- 以此类推,可以得到1234的每一位
- 实现print函数进行打印
- 代码(print(1234)==print(123)+printf(4))
实现
void Print(int n)
{
if (n > 9)
Print(n /10);
printf("%d ", n % 10);
}
int main()
{
int n = 0;
scanf("%d", &n);
Print(n);
return 0;
}
把Print(1234) 打印1234每⼀位,拆解为⾸先Print(123)打印123的每⼀位,再打印得到的4
把Print(123) 打印123每⼀位,拆解为⾸先Print(12)打印12的每⼀位,再打印得到的3
直到Print打印的是⼀位数,直接打印就⾏
4.递归和迭代
递归是一种很好的编程技巧,但也可能被误用
int Fact(int n)
{
if(n<=0)
return 1;
else
return n*Fact(n-1);
}
解释
Fact函数是可以产生正确结果,但在递归函数被调用的过程中设涉及一些运行时的开销,在C语言中的每一次函数调用,都要需要为本次函数调用在栈区申请一块内存空间来保存函数调用期间的各种局部变量的值,这块空间被称为运行时堆栈,或者函数栈帧,函数不返回,函数对应的栈帧空间就一直被占用,所以如果函数调用中存在递归调用的话,每一次递归函数调用都会开辟属于自己的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间.所以如果采用函数递归的方式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出问题
如果不想使用递归就得想其他办法,通常就是迭代的方式(通常是循环)
计算n的阶乘,也是可以产生1~n的数字累计乘在一起
int main()
{
int n = 0;
scanf("%d", &n);
int i = 0;
int ret = 1;
for (i = 0; i < n; i++)
{
ret = ret * i;
}
printf("%d\n", ret);
return 0;
}
上述代码能够完成任务,并且效率比递归方式更好
事实上,很多问题都是以递归的形式进行解释的,只是因为它比非递归形式更加清晰,但如果迭代实现就会效率方面比递归更高,当一个问题很复杂时,递归实现的简洁性可以补偿它所带来的运行开销
举例3.求第n个斐波那契数
可以看到递归程序会不断展开,在展开过程中,会有大量的重复计算,递归层次越深 冗余计算就越多,也就意味着当我们输入一个不太大的数,比如40,50就会计算很久。
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("%d\n", count);
return 0;
}
可以看到,在计算第40个斐波那契数,第3个斐波那契数就被重复计算了很多次,这些计算都是冗余的,所以在计算斐波那契数使用递归是不合适的,这是就可以使用迭代方式
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;
}
这样实现效率就很高,也避免了大量的冗余计算