一:递归是什么?
递归是一种解决问题的方法,在C语言中递归就是函数自己调用自己。但如果递归层次太深,则会导致栈溢出(Stack overflow)。
下面举一个例子,代码如下:
#include<stdio.h>
int main()
{
printf("你好!\n");
main();
return 0;
}
如此一来,内存中栈调用的层次太深而导致栈溢出。
二:递归的算法思想
把一个大型复杂问题层层转化为一个与原问题相似,但规模较小的子问题来求解,直到子问题不能再被拆分,递归结束。
所以递归的核思想就是“大事化小”的过程。
在递归中,“递”就是递推,“归”就是回归。接下来将进一步展开。
三:递归的约束条件
递归在书写时有两个限制条件:
1、递归存在限制条件,当程序满足这个限制条件时,递归结束。
2、每次调用递归程序后,则会越来越接近这个限制条件。
四、递归举例
1、阶乘的递归
#include<stdio.h>
int Fac(int n)
{
if (n == 0 || n == 1)
return 1;
else
{
return n * Fac(n - 1);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d\n", Fac(n));
return 0;
}
代码解析如下:
(1)递归过程
(2)递归在内存中栈帧的建立与释放
(3)画图演示
五:递归与迭代对比,以及递归的栈溢出问题
5.1栈溢出问题
在使用递归时,我们了解到每递推一次,就会在栈内开发一个空间,但如果此递归函数没有限制条件或者递推层次过深时,就会发生栈溢出情况,也就是在栈内开发的空间过多,导致栈的空间承载不了递归函数。这时,递归函数不仅不会完成它原本的使命,反而会给内存增加负担。
就如本文开头时所举的例子,就是一个典型的栈溢出问题。而栈溢出对于上面刚刚写到的阶乘代码,同样会出现此类情况。
运算结果如下:
(1)在n=5时,栈内空间足够,程序正常运行。
(2)在n=100时,栈溢出,结果错误。
所以大家在使用递归时,要充分考虑到这种递归层次过深的情况。
5.2迭代与递归的对比
递归是一种很好的编程技巧,但是和很多技巧一样,也是可能被误用的。
Fac函数是可以产生正确的结果,但是在递归函数调用的过程中涉及一些运行时的开销。
在C语言中每一次函数调用,都需要为本次函数调用在栈区申请一块内存空间来保存函数调用期间的各种局部变量的值,这块空间被称为运行时堆栈,或者函数栈帧。
函数不返回,函数对应的栈帧空间就会一直占用,所以如果在函数调用中存在递归调用的话,每一次递归函数调用都会开辟属于自己的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。
所以如果采⽤函数递归的方式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(Stack overflow)的问题。
下面以斐波那契数为例:
斐波那契数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
测试代码如下:
#include<stdio.h>
int Fib(int n)
{
if (n == 1 || n == 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d\n", Fib(n));
return 0;
}
在上面代码中,如果我们输入50,则需要很长的时间才能得出结果,甚至更大的数字有可能会发生栈溢出的情况。那是因为在代码运行时,同一个数字有可能会被重复调用成百上千便,这就大大增加了运行压力。如下图:
但如果转换一种思路,使用迭代的思想,那么这种问题将会简单许多。
测试代码:
#include<stdio.h>
int Ite(int n)
{
int a = 1;
int b = 1;
int c = 1;
if (n < 3)
return c;
else
{
while (n >= 3)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d\n", Ite(n));
return 0;
}
六:总结
不管是递归算法还是迭代算法都要根据具体的实例来使用,只有多多思考,找到每种算法的契合点,才能写出高效的程序。