C语言 函数递归

1.递归是什么

在C语言中,递归就是函数自己调用自己。

我们来看一个最简单的递归:

#include <stdio.h>
int main()
{
 printf("hehe\n");
 main();//main函数中⼜调⽤了main函数
 return 0;
}

这就是一个简单的递归程序,只不过上面的递归最后会陷入死递归,只是为了让大家先认识一下递归。

递归的思想:

把一个大型复杂问题层层转化为一个与原问题相似,但是规模较小的子问题来求解;直到子问题不能再被拆分,递归就结束了。所以递归的思考方式就是把大事化小的过程。

递归中的递就是递推的意思,归就是回归的意思。

2.递归的限制条件

递归在书写的时候,有两个必要条件:

1.递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。

2.每次递归调用之后越来越接近这个限制条件。

3.递归举例

3.1举例1:求n的阶乘

我们先从具体的数字开始:
 

举例:
        5!= 5*4*3*2*1
        4!= 4*3*2*1
所以5!= 5*4!

我们可以类比得到:
n!= n*(n-1)!

(n-1)!=(n-1)*(n-2)!

(n-2)!=(n-2)*(n-3)!

......

直到n等于1或者0时,就不再分解

再稍微分析一下,当n<=1的时候,n的阶乘是1,其余n的阶乘都是可以通过上述关系推导。

所以n的阶乘的递归公式如下:

我们假设函数Fact(n)就是求n的阶乘,函数如下:

int Fact(int n)
{
	if (n <= 1)
		return 1;
	else
		return n * Fact(n - 1);
}

完整代码测试:
 

#include <stdio.h>

int Fact(int n)
{
	if (n <= 1)
		return 1;
	else
		return n * Fact(n - 1);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fact(n);
	printf("%d", ret);
	return 0;
}

这里不考虑n太大的情况,n太大存在溢出。

3.2举例2:顺序打印一个整数的每一位

输入一个整数n,按照顺序打印这个整数的每一位。

比如:输入:1234   输出:1 2 3 4

我们先分析一下这个问题:

如果n是一位数,就直接打印n;如果n超过一位数,我们就要拆分每一位。以1234为例,1234%10就得到4,然后1234/10就得到123,然后123%10就得到3,再除10得到12,以此类推,不断地%10和/10操作,直到1234的每一位都被拿出来,但是有个问题就是得到的数字顺序是倒着的。

但是我们发现一个数字的最低位是最容易得到的,那我们假设想写一个函数Print来打印n的每一位,如下表示:

Print(n)
如果n是1234,那表⽰为
Print(1234) //打印1234的每⼀位
其中1234中的4可以通过%10得到,那么
Print(1234)就可以拆分为两步:
1. Print(1234/10) //打印123的每⼀位
2. printf(1234%10) //打印4
完成上述2步,那就完成了1234每⼀位的打印
那么Print(123)⼜可以拆分为Print(123/10)+printf(123%10)

完整代码实现:

#include <stdio.h>

void Print(int n)
{
	if (n <= 9)
		printf("%d ", n);
	else
	{
		Print(n / 10);
		printf("%d ", n % 10);
	}
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	Print(n);
	return 0;
}

运行结果:

4.递归与迭代

递归是复杂问题很好的一个解决方法,但是也可能被误用,就像举例1中,看到推导的公式,很容易写成递归的形式:

int Fact(int n)
{
	if (n <= 1)
		return 1;
	else
		return n * Fact(n - 1);
}
Fact函数是可以产⽣正确的结果,但是在递归函数调⽤的过程中涉及⼀些运⾏时的开销。
在C语⾔中每⼀次函数调⽤,都要需要为本次函数调⽤在栈区申请⼀块内存空间来保存函数调⽤期间的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧。
函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次递归函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(stack overflow)的问题。
所以如果不想使用递归就得想其他的办法,通常就是迭代的方式(循环)。
比如:计算n的阶乘,也可以是产生1-n的数字乘在一起。
int Fact(int n)
{
 int i = 0;
 int ret = 1;
 for(i=1; i<=n; i++)
 {
 ret *= i;
 }
 return ret;
}

上述代码能完成任务而且效率比递归更高。

4.1求第n个斐波那契数

首先我们考虑递归的方式

看到这个公式,很容易诱导我们写成递归的形式,如下:

#include <stdio.h>

int Fib(int n)
{
	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", ret);
	return 0;
}

当我们输入50的时候,会发现需要很长时间才能算出结果,这说明递归的写法是非常低效的,那是为什么呢?

其实递归程序会不断地展开,在展开的过程中,我们很容易就能发现,递归的过程中会有重复的计算,而且递归层次越深,重复的计算就会越多,我们来测试一下:
 

#include <stdio.h>

int count = 0;
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", ret);
	printf("\n");
	printf("count=%d",count);
	return 0;
}

可以看到,当计算第23个斐波那契数时,第3个斐波那契数被重复计算了10946次,看得出来这个算法是非常低效的。所以我们就考虑用迭代的方式计算。
下面是用迭代实现的:
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;
}
用这个方式,效率就会高很多。
 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dddyy.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>