C语言之函数递归与迭代

目录

1.什么是函数递归?

2.递归的思想

3.递归的限制条件

4.递归举例

        4.1 求n的阶乘

4.2 顺序打印⼀个整数的每⼀位

5.递归与迭代

5.1 求第n个斐波那契数


1.什么是函数递归?

在学习C语言的过程中,大家肯定都听说过递归,那么递归其实就是函数自己调用自己,递归是一种解决问题的方法。

2.递归的思想

把⼀个⼤型复杂问题层层转化为⼀个与原问题相似,但规模较⼩的⼦问题来求解;直到⼦问题不能再被拆分,递归就结束了。所以递归的思考⽅式就是把⼤事化⼩的过程。 递归中的递就是递推的意思,归就是回归的意思。

接下来我们看一段简单的代码:

​
int main()
{
    printf("haha\n");
    main();//在main函数中不断调用main函数
    return 0;
}

​

运行结果:

 

我们发现这个简单的递归程序陷入了死循环 这是因为我们并没有设置递归的限制条件,所以main函数一直不断的调用自己,这时候程序不仅陷入了死循环,并且造成了栈溢出(Stack overflow)的现象:

可见在使用递归时,限制条件的重要性。

3.递归的限制条件

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

• 递归存在限制条件,当满⾜这个限制条件的时候,递归便不再继续

• 每次递归调⽤之后越来越接近这个限制条件

4.递归举例

        4.1 求n的阶乘

题⽬:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。

注:0的阶乘等于1

接下来我们来分析一下这道题该怎么解决:

//5的阶乘
5!=5 * 4 * 3 * 2 * 1
//4的阶乘
4!=4 * 3 * 2 * 1
以此类推...

那么我们是不是可以将5的阶乘看成:
5的阶乘=5 * 4的阶乘
5!=5 * 4!(4的阶乘)

而4的阶乘看成
4的阶乘=4 * 3的阶乘
4!=4 * 3!(3的阶乘)
以此类推...

通过上面分析我们是不是可以得出 n的阶乘公式呢:n! = n * (n-1)! ,只要n>0的时候,那么 n! = n * (n-1)!,当n=0时,n!=1;这时候我们是不是可以将这个运算公式当成数学中的分段函数看待呢?

这个其实也就是n的阶乘的递归公式,Fact(n)就是n的阶乘, 接下来我们根据这个公式尝试写一下代码:

​
​
#include <stdio.h>

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


​

运行结果:

我们结合过程图和代码分析一下:

当第一次进入Fact(n)函数时,此时的n等于5,经过判断返回n*Fact(n-1)也就是5*4!,此时再次调用 Fact(n-1) 函数,通过n-1,此时的n为4,再次经过判断返回 n*Fact(n-1),但是此时的n为4,那么返回的就是4*3!,再次调用Fact(n-1)函数,以此类推,此时就是递归中递推的过程。直到调用到n=0时,经过 if 判断,返回1;此时就不再继续调用函数Fact,开始回归,将Fact(0)的值返回到Fact(1)函数中,Fact(1)通过Fact(0)返回的值1,计算表达式n*Fact(0) (1*1);再将表达式的值返回给Fact(2)中,Fact(2)再通过Fact(1)返回的值1,完成表达式n*Fact(1)(2*1)以此类推,最后将Fact(4)的返回值24返回到Fact(5)中的表达式n*Fact(4) (5*24),最后将Fact(5) 的返回值返回到主函数中,并且打印出来。可以发现每次调用函数时,n的值不断接近我们的限制条件n=0,当n=0时,函数开始回归。这就满足了递归的限制条件。

4.2 顺序打印⼀个整数的每⼀位

输⼊⼀个整数m,按照顺序打印整数的每⼀位。

⽐如:

输⼊:1234 输出:1 2 3 4

输⼊:520 输出:5 2 0

 看到这个题目,我们的思路应该是将这组数字的每一位数一个一个取出来,比如:

1234 我们通过1234%10将4给取出来,然后将1234/10,得到123

再通过123%10将3取出来,再将123/10得到12

以此类推

这样我们不就能将每一位数取出来了吗?

那我们假设想写⼀个函数Print来打印n的每⼀位,如下表⽰: 

print(n)
如果n是1234,那表⽰为
Print(1234) //打印1234的每⼀位


其中1234中的4可以通过1234%10得到

那么此时将1234/10得到123,再次调用print(n)


此时print中的n为123,通过123%10取出3

将123/10得到12,再次调用print(n)函数

以此类推

当只剩下1时,我们将1取出来,就不再调用 print(n)

那这不就是递归吗?

既然我们已经有思路后,那么就以代码的方式实现吧 

void Print(int n)
{
	if (n > 9)
	{
		Print(n / 10);
	}
	printf("%d ", n % 10);
}

int main()
{
	int m = 0;
	scanf("%d", &m);
	Print(m);
	return 0;
}

输入和输出结果: 

 

结合画图和代码进行解读分析:

 

在这个解题的过程中,我们就是使⽤了⼤事化⼩的思路

把Print(1234) 打印1234每⼀位,拆解为⾸先Print(123)打印123的每⼀位,再打印得到的4 把Print(123) 打印123每⼀位,拆解为⾸先Print(12)打印12的每⼀位,再打印得到的3 直到Print打印的是⼀位数,直接打印就⾏。 

5.递归与迭代

递归是⼀种很好的编程技巧,但是和很多技巧⼀样,也是可能被误⽤的,就像举例1⼀样,看到推导的 公式,很容易就被写成递归的形式

但是在C语⾔中每⼀次函数调⽤,都需要为本次函数调⽤在内存的栈区,申请⼀块内存空间来保存函数调 ⽤期间的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧。 函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次递归 函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。 所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢 出(stack overflow)的问题。

所以如果不想使⽤递归,就得想其他的办法,通常就是迭代的⽅式(通常就是循环的⽅式)

⽐如:计算 n 的阶乘,也是可以通过循环的方式计算

int main()
{
	int m = 0;
	int sum = 1;
	scanf("%d", &m);
	for (int i = 1; i <= m; i++)
	{
		sum *= i;
	}
	printf("%d", sum);
	return 0;
}

当⼀个问题⾮常复杂,难以使⽤迭代的⽅式实现时,此时递归实现的简洁性便可以补偿它所带来的运 ⾏时开销。 除此之外,使用迭代的方式解决问题或许更加通俗易懂,实现的效率也更高。

5.1 求第n个斐波那契数

斐波那契数就是的每个数字是前两个数字的总和,例如1,1,2,3,5,8,13,21 .........

根据这个规律,我们就可以得到公式:

既然有了公式之后,那么我们就用递归的方式实现一下这道题目:
 

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\n", ret);
	return 0;
}

 但是当我们n输⼊为50的时候,需要很⻓时间才能算出结果, 这也说明递归的写法是⾮常低效的,那是为什么呢?

 其实递归程序会不断的展开,在展开的过程中,我们很容易就能发现,在递归的过程中会有重复计 算,⽽且递归层次越深,冗余计算就会越多。

所以斐波那契数的计算,使⽤递归是⾮常不明智的,我们就得想迭代的⽅式解决。 我们知道斐波那契数的前2个数都1,然后前2个数相加就是第3个数,那么我们从前往后,从⼩到⼤计 算就⾏了。

int main()
{
	int n = 0;
	scanf("%d", &n);
	int a = 1;
	int b = 1;
	int c = 1;
	while (n > 2)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	printf("%d\n", c);
	return 0;
}

使用迭代的方式实现这道题目,效率就会快很多。

那么递归与迭代,我们该如何选择正确的方式解决问题呢?

递归的好处:
        1.递归往往只允许少量的代码,就完成了大量重复的计算

        2.在特定的场景下递归代码写起来非常方便 

当条件满足上面的内容时,选择递归往往能够更高效的完成任务。而当冗余计算过多,往往选择迭代较为合适,也不容易出现栈溢出的现象。

以上就是本期C语言中的递归与迭代,希望大家看完后能够认识递归与迭代,并且在遇到问题时能够正确选择合适的方法解决问题,如有错误,欢迎评论指出~谢谢大家!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值