递归原来如此,带你深入探究递归的秘密!!!

1 什么是递归?

程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
也可以从字面上理解就是递进和回归

2 递归的必要条件

  • 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
  • 每次递归调用之后越来越接近这个限制条件。

3 递归的执行过程

下面我们通过几段代码分析下递归的执行过程
🔑栗子一:

#include <stdio.h>
void print(int n)
{
    if(n>9)
   {
    print(n/10);
   }
    printf("%d ", n%10);
}
int main()
{
    int num = 1234;
    print(num);
    return 0;
}

这段代码的功能接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4

我们来画图分析一下这段代码
在这里插入图片描述

首先print函数接收num的值1234开始进入函数体执行,当进行完if语句判断后满足条件开始下一次print函数的执行(递进),此时第二个print函数接收的值为n/10。也就是123,然后再次进行判断,此时仍然满足条件,继续递进,此时第三个print函数接收的值为12,仍然满足if语句执行条件,再次递进,此时第四个print函数的接收值为1,不满足条件,这个时候我们开始回归,首先第四个print函数输出1,然后返回到第三个函数里面跳出if循环体执行printf语句输出2,再次返回到第二个函数里面跳出循环体执行printf语句输出3,最后返回到第一个print函数里面执行printf语句输出4,此时回归完成,从而返回主函数执行下一条语句。
注意:在递进的过程中我们可以发现每递进一次函数接收的值就会越来越小,当小到不满足循环条件时才会进行返回,这就符合了我们刚刚谈到的递归的两个必要条件。

🔑栗子二:

#include <stdio.h>
int Strlen(const char* str)
{
	if (*str == '\0')
		return 0;
	else return 1 + Strlen(str + 1);
}
int main()
{
	char* p = "abcd";
	int len = Strlen(p);
	printf("%d\n", len);
	return 0;
}

这个函数时用来求字符串的长度的,如果要求编写函数不允许创建临时变量,我们就可以用递归解决问题。

我们再来画图分析一下这段代码
在这里插入图片描述

首先第一个Strlen接收到的字符串abcd的地址,此时if语句不满足条件,进行递进,第二个Strlen接收到的字符串bcd的地址,此时if语句任然不满足,继续递进,第三个Strlen接收到的是cd的地址,继续递进,直到在第五个Strlen出此时接收到是’\0’的地址,此时if条件满足,开始返回,在第四个Strlen函数中返回1+0,在第三个Strlen函数中返回1+1+0,一直返回到第一个Strlen函数,此时返回值变成了1+1+1+1+0(即返回4),然后将返回值带回到主函数中执行下一条语句。
注意:这里限制条件就是*str == '\0',而且每一次递进都越来越接近这个限制条件,否则程序将会死循环

4 递归与迭代

迭代可以简单的理解为循环。在经过上面大家可能觉得递归yyds,虽然递归代码写起来比较简单,但实际上有些问题用递归反而更加复杂,这个时候我们就要考虑用非递归的方法来设计函数,比如迭代。
💡:我们来举个简单例子

求第n个斐波那契数。(不考虑溢出)

小课堂之斐波那契数列:
斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列: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*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用。

代码:

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;
}

代码看起来很简单,但是当我们输入一个比较大的数的时候就会有一些问题。

在这里插入图片描述

这里我们输入50的时候程序短时间就已经加载不出来了,这是因为在递归的过程中程序反复调用,像输入50的话,程序会调用49和48的函数,49,48又会继续调用48,47,47,46,这样到最后会调用数不清次函数,这样会大大拖延运行效率。

  • 在调试 factorial 函数的时候,如果你的参数比较大,那就会报错: `stack overflow(栈溢出) 这样的信息。
  • 系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。

那么如何解决上面的问题呢,我们可以采用迭代。

#include <stdio.h>
int fib(int n)
{
	int a1 = 1;
	int a2 = 1;
	int a3 = 0;
	int i = 0;
	for (i = 3; i <= n; i++)
	{
		a3 = a1 + a2;//斐波那契数就是一个数等于前面两个数的和
		a1 = a2;//用后面两个数替代前面两个数,这样就可以反复利用这两个变量来计算后面的数
		a2 = a3;
	}
	return a2;
}

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

结果:
在这里插入图片描述

我们可以看到这个时候哪怕算一个很大的数字基本也能很快算出结果。
总结:

  1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
  2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
  3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。

作者水平有限,如果有错误欢迎各位大佬指正,也欢迎大家一起交流一起学习!!!
❤️作者gitee链接: https://gitee.com/zeng-xiwena.欢迎来访哈哈哈

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值