递归是c语言学习上绕不开的话题,那么什么是递归呢?
递归实际上是自己调用自己。
递归在书写的时候有两个限制条件:
递归存在限制条件,当满足这个限制条件式,递归将不再继续。
每次递归调用都越来越接近这个限制条件。
一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1.
那n的阶乘就可以写为:
n!= n * (n - 1)!
n - 1的阶乘又可以写为:
(n - 1)! = (n - 1) * (n - 2)!
...
最后得到2的阶乘:
2! = 2 * 1
这样我们就可以认为n! 从n * (n - 1)开始右边的乘数将会重复调用m * (m- 1) 这个函数,函数传参为n - 1,当传参被减为1时则停止调用(递归的限制条件)。这样调用函数的方式就被称为递归。
int fac(int m)
{
int x = 0;
if (n > 1)
{
return fac(m - 1) * m;
}
else
{
return 1;
}
}
int main()
{
int x = 0;
int n = 0;
while(scand("%d",n)==1)
{
x = fac(n);
}
printf("%d", x);
return 0;
}
比如当我们输入4时就是按照下面的方式执行的
向下展开的过程被称为递推,递推完后向上返回结果的过程被称为回归。
我们可以知道a%10后可以得到a的个位,a/10后可以删去a的个位。
那我们就可以这样写:
int main()
{
int a = 1234;
int ret = 0;
while(a > 0)
{
ret = a % 10;
a /= 10;
printf("%d ", ret);
}
return 0;
}
但这个代码是倒序打印一个整数的每一位,不符合题目的要求。那有什么方法能让它先打印整数的最大位呢?这时就要运用到递归了,递归是先递推把a展开后,然后再回归打印整数的每一位。
void A(int n)
{
int ret = 0;
if (n > 10)
{
ret = n % 10;
A(n / 10);
printf("%d ", ret);
}
else
printf("%d ", n);
}
int main()
{
int n = 0;
scanf("%d", &n);
A(n);
return 0;
}
递归是⼀种很好的编程技巧,但是很多技巧⼀样,也是可能被误⽤的。
在递归函数调⽤的过程中涉及⼀些运⾏时的开销。
在C语⾔中每⼀次函数调⽤,都要需要为本次函数调⽤在栈区申请⼀块内存空间来保存函数调⽤间
的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧。
函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次归
函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧间。
所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起溢
出(stack overflow)的问题。
所以不想用递归就得想其他的方法,通常就是迭代的方式(通常就是循环的方式)。
例如,上面的举例1我们也可以使用循环的方式进行实现。
Fact(int n)
{
int ret = 1;
for (int i = 1; i <= n; i++)
{
ret *= i;
}
return ret;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fact(n);
printf("%d", ret);
return 0;
}
上诉代码的效率是比递归方式效率高的。
事实上,我们看到的许多问题是以递归的形式进⾏解释的,这只是因为它⽐⾮递归的形式更加晰,
但是这些问题的迭代实现往往⽐递归实现效率更⾼。
当⼀个问题⾮常复杂,难以使⽤迭代的⽅式实现时,此时递归实现的简洁性便可以补偿它所带来的运⾏时开销。
A(int n)
{
if (n > 2)
return A(n - 1) + A(n - 2);
else
return 1;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = A(n);
printf("%d", ret);
return 0;
}
int main()
{
int a = 1, b = 1, c;
int n = 0;
scanf("%d", &n);
if (n > 2)
{
while (n - 2)
{
c = a;
a = b;
b = b + c;
n--;
}
}
printf("%d", b);
return 0;
}
虽然上面的两种方法都可以得到正确的结果,但是使用递归的方法时,我们会发现这个方法计算的非常的慢,而使用迭代的方法计算则可以立即得到结果。(如下图)
这是因为每次递归调用都会在内存中创建一个新的栈帧来保存局部变量和返回地址,而导致内存的使用和时间开销的增加。