文章目录
一、什么是递归
递归是学习c语言函数绕不开的话题,那么什么是递归呢?
C允许函数调用他自己,这种调用过程称为递归(recursion)。递归有时难以理解,有时又方便实用。结束递归是使用递归的重难点,因为如果递归代码中没有终止递归的条件部分,一个调用自己的函数就会无限的递归。
写一个史上最简单的C语言递归代码:
#include<stdio.h>
int main()
{
printf("hehe\n");
main();//main函数中继续调用main函数
return 0;
}
最简单的递归方式但是它是错误的因为它无限递归下去后导致了栈溢出,然后导致编译器报错。
1.1 递归思想(注意仔细体会)
把一个大型复杂的问题转化为一个与原问题相似,但规模较小的子问题来求解,直到子问题不能再被拆分,递归就结束了,通俗的来讲就是大事化小
递归中的递就是递推的意思,归就是回归的意思。
1.2递归递归的限制条件
递归在书写的时候,有两个必要条件:
- 递归存在限制条件,当满足这个条件的时候,递归便不再继续。(就是不能无限制的递归下去)
- 每次递归调用之后越来越接近限制条件。(有机会让它停下来)
注意有限制条件也有可能完成不了需求
例如:递归层数过多可能导致栈溢出
二、递归举例
2.1 举例1:求n的阶乘
一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1 自然数n的阶乘写作n!。
题目:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。
根据上面的分析我们可以大事化小,每次都调用值为n-1的函数直到n=0.
这样的思路就是把一个较大的问题转化为一个与原问题相似,但规模较小的问题来求解。
当n==0
的时候,n的阶乘是1,其余n的阶乘都是可以通过公式计算。
n的阶乘的递归公式如下:
那么我们就可以写出函数Fact求n的阶乘,假设Fact(n)就是求n的阶乘,那么Fact(n-1)就是求n-1的阶乘,函数如下:
Fact(int n)
{
if(n==0)
{
return 1;
}
else
{
return n*Fact(n-1);
}
}
所以我们就可以得到下面的代码
#include<stdio.h>
Fact(int n)
{
if(n==0)
{
return 1;
}
else
{
return n*Fact(n-1);
}
}
int main()
{
int n;
scanf("%d",&n);
int ret = Fact(n);
printf("%d",ret);
return 0;
}
这里不考虑n特别大情况
根据下面图理解一下整体代码
每一次函数调用,都会向内存申请一块空间(这块空间叫做运行时堆栈或函数栈帧空间可以看内存四区这篇文章,从下到上创建,从上到下销毁),来储存函数调用过程中的相关信息
具体逻辑如下图所示:
2.2 举例2:顺序打印一个整数的每一位
题目:输入一个整数m,按照顺序打印整数的每一位。
比如:
输入:1234 输出: 1 2 3 4
输入:520 输出: 5 2 0
2.2.1分析与实现
上面是一个简单的分离的思路
现在我们要尝试将它写成代码
首先,我们要假设一个Print函数,假设它可以帮我们完成目的
它的功能是下面的样子:
我们知道它的主要功能后,接下来我们开始实现它
void Print(int n)
{
if(n > 9)
{
Print(n / 10);
}
else
{
printf("%d",n % 10);
}
}
到这里我们就清晰了
因此完整的解题的代码
#include<stdio.h>
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;
}
注意这里只是if语句没有else
画图来解释一下
三、递归与迭代
递归是一种很好的编程技巧,但是和很多技巧一样,也可能被误用的,就像举例1一样,看到推导的公式,很容易就被写成递归的形式
在c语言中每一次函数调用,都需要为本次函数调用在内存的栈区,申请一块内存空间来保存函数调用期间的各种局部变量的值,这块空间被称为运行时堆栈,或者函数栈帧。
函数不返回,函数对象的栈帧空间就一直占用,所以如果函数调用中存在递归调用的话,每一次递归函数调用都会开辟属于自己的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。
所以如果采用函数递归的方式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(Stack Overflow)的问题。
所以如果不想使用递归的,就得想其他的方法,通常就是迭代的方式(通常就是循环的方式)。
比如:计算n的阶乘,也可以产生1~n的数字累计乘在一起的。
事实上,我们看到的许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更加清晰,但是这些问题的迭代实现往往比递归实现效率更高。
当一个问题非常复杂,难以使用迭代的方式实现时,此时递归实现的简介性便可以补偿它所带来的运行时开销。
3.1 举例3:斐波那契数
我们也能举出更加极端的例子,就像计算第n个斐波那契数
大概的流程就是下面的图片里的
代码实现:
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;
}