c语言回顾-函数递归

1.递归的介绍

1.1什么是递归

递归是指在一个函数的定义中调用自身的过程。简单来说,递归是一种通过重复调用自身来解决问题的方法。

递归包括两个关键要素:基本情况和递归情况。基本情况是指当问题达到某个特定条件时,不再需要递归调用,可以直接返回结果。递归情况是指在解决问题的过程中,通过调用自身来缩小问题规模,直到达到基本情况。

在c语言中,递归就是函数自己调用自己。
写一个史上最简单的C语言递归代码:
#include <stdio.h>
int main()
{
 printf("hehe\n");
 main();//main函数中⼜调⽤了main函数
 return 0;
}
上述就是⼀个简单的递归程序,只不过上面的递归只是为了演示递归的基本形式,不是为了解决问
题,代码最终也会陷入死递归,导致栈溢出(Stack overflow)。

1.2递归的思想

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

1.3递归的限制条件

递归在书写的时候,有2个必要条件:
• 递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。
• 每次递归调用之后越来越接近这个限制条件。
在下面的例子中,我们逐步体会这2个限制条件。

2.递归举例

2.1 举例1:求n的阶乘

一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。
自然数n的阶乘写作n!。
题目:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。

2.1.1 分析和代码实现

我们知道n的阶乘的公式: n! =   n ∗ ( n − 1)!
举例:
5! = 5*4*3*2*1
4! = 4*3*2*1
所以:5! = 5*4!
依次类推
这样的思路就是把一个较大的问题,转换为一个与原问题相似,但规模较小的问题来求解的。
当 n==0 的时候,n的阶乘是1,其余n的阶乘都是可以通过公式计算
n的阶乘的递归公式如下:
61c71e60dbd94442ad08b417755cbdae.png
假设Fact(n)就是求n的阶乘,那么Fact(n-1)就是求n-1的阶 乘,函数如下:
int Fact(int n)
{
if(n==0)
return 1;
else
return n*Fact(n-1);
}

测试代码:

#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\n", ret);
	return 0;
}
运行结果(这⾥不考虑n太大的情况,n太大存在溢出):
78b40fa2833b44bcaf5735b32ab064d4.png

2.1.2 画图推演

41481e4bb8a1442aa41570f71bdeb09d.png

f67a68cf70e24fc39a01962f6a4edbd2.png

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

输入一个整数m,打印这个按照顺序打印整数的每一位。
⽐如:
输⼊:1234 输出:1 2 3 4
输⼊:520 输出:5 2 0

2.2.1 分析和代码实现

这个题目,放在我们面前,首先想到的是,怎么得到这个数的每一位呢?
如果n是一位数,n的每一位就是n自己
n是超过1位数的话,就得拆分每一位
1234%10就能得到4,然后1234/10得到123,这就相当于去掉了4
然后继续对123%10,就得到了3,再除10去掉3,以此类推
不断的 %10 和 /10 操作,直到1234的每一位都得到;
但是这里有个问题就是得到的数字顺序是倒着的
但是我们有了灵感,我们发现其实一个数字的最低位是最容易得到的,通过%10就能得到
那我们假设想写一个函数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)
以此类推下去,就有
Print(1234)
==>Print(123) + printf(4)
==>Print(12) + printf(3)
==>Print(1) + printf(2)
==>printf(1)
直到被打印的数字变成一位数的时候,就不需要再拆分,递归结束。
代码实现:
#include <stdio.h>
void Print(int n) {
	if (n > 9) {
		Print(n / 10);
		printf("%d ", n % 10);
	}
	else
		printf("%d ", n % 10);
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	Print(n);
	return 0;
}

简化版本:

我们发现if 和else 语句中都有printf("%d ",n%10);故可以简化。

void Print(int n) {
	if (n > 9) {
		Print(n / 10);
	}
		printf("%d ", n % 10);
}
在这个解题的过程中,我们就是使用了大事化小的思路
把Print(1234) 打印1234每一位,拆解为首先Print(123)打印123的每一位,再打印得到的4
把Print(123) 打印123每一位,拆解为首先Print(12)打印12的每一位,再打印得到的3
直到Print打印的是一位数,直接打印就行。

2.2.2 画图推演

以1234每一位的打印来推演一下
b19274e0cd50429f96280ada351de4b8.png

3.递归与迭代

递归是一种很好的编程技巧,但是很多技巧一样,也是可能被误用的,就像举例1一样,看到推导的公 式,很容易就被写成递归的形式:
int Fact(int n)
{
 if(n==0)
 return 1;
 else
 return n*Fact(n-1);
}
Fact函数是可以产生正确的结果,但是在递归函数调用的过程中涉及一些运行时的开销。
在C语言中每一次函数调用,都要需要为本次函数调用在栈区申请一块内存空间来保存函数调用期间的各种局部变量的值,这块空间被称为运行时堆栈,或者函数栈帧。
函数不返回,函数对应的栈帧空间就一直占用,所以如果函数调用中存在递归调用的话,每一次递归函数调用都会开辟属于自己的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。 所以如果采用函数递归的方式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢 出(stack overflow)的问题。
所以如果不想使用递归就得想其他的办法,通常就是迭代的方式(通常就是循环的方式)。
像上面求n的阶乘也可以用迭代的方法
int Fact(int n)
{
 int i = 0;
 int ret = 1;
 for(i=1; i<=n; i++)
 {
 ret *= i;
 }
 return ret;
}
事实上,我们看到的许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更加清晰, 但是这些问题的迭代实现往往比递归实现效率更高。
当一个问题非常复杂,难以使用迭代的方式实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
典型的例子就是求斐波拉契数列

举例3:求第n个斐波那契数

我们也能举出更加极端的例子,就像计算第n个斐波那契数,是不适合使用递归求解的,但是斐波那契数的问题通过是使用递归的形式描述的,如下:
Fib(n)= 1(n<=2)   
         =Fib(n-1)+Fib(n-2)  (n>2)
int Fib(int n)
{
 if(n<=2)
 return 1;
 else
 return Fib(n-1)+Fib(n-2);
}
但是当我们n输入为50的时候,需要很长时间才能算出结果,这也说明递归的写法是非常低效的,那是为什么呢?
46ffe5cfdb9a4c9eaf742fa3763f1f7b.png
其实递归程序会不断的展开,在展开的过程中,我们很容易就能发现,在递归的过程中会有重复计算,而且递归层次越深,冗余计算就会越多。可以通过代码测试:
#include <stdio.h>
int count = 0;
int Fib(int n)
{
 if(n == 3)
 count++;//统计第3个斐波那契数被计算的次数
 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); 
 printf("\ncount = %d\n", count);
 return 0;
}

11d5e8a9f3244d528f45c4f3587637ff.png

这里我们看到了,在计算第40个斐波那契数的时候,使用递归方式,第3个斐波那契数就被重复计算了39088169次,这些计算是⾮常冗余的。所以斐波那契数的计算,使用递归是非常不明智的,我们就得 想迭代的方式解决。
我们知道斐波那契数的前2个数都1,然后前2个数相加就是第3个数,那么我们从前往后,从小到大计算就行了。
int Fib(int n)
{
	int a = 1, b = 1, c =0;
	while (n > 2)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	return c;
}

通过迭代方法计算,效率会高很多!!!

OK,本节内容到此结束,递归的理解重点是要画图。

支持小编的友友留下三连和评论吧!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/701485.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot整合SpringDataRedis

目录 1.导入Maven坐标 2.配置相关的数据源 3.编写配置类 4.通过RedisTemplate对象操作Redis SpringBoot整合Redis有很多种&#xff0c;这里使用的是Spring Data Redis。接下来就springboot整合springDataRedis步骤做一个详细介绍。 1.导入Maven坐标 首先&#xff0c;需要导…

LLM应用实战:当图谱问答(KBQA)集成大模型(三)

1. 背景 最近比较忙(也有点茫)&#xff0c;本qiang~想切入多模态大模型领域&#xff0c;所以一直在潜心研读中... 本次的更新内容主要是响应图谱问答集成LLM项目中反馈问题的优化总结&#xff0c;对KBQA集成LLM不熟悉的客官可以翻翻之前的文章《LLM应用实战&#xff1a;当KBQ…

弘君资本:苹果股价暴涨,创历史新高!

当地时间6月11日&#xff0c;美股三大指数涨跌纷歧&#xff0c;标普500指数与纳指再创新高。 到收盘&#xff0c;道指跌0.31%&#xff0c;纳指涨0.88%&#xff0c;标普500指数涨0.27%。 苹果大涨逾7%创前史新高。美联储开端召开6月货币方针会议&#xff0c;周三发布利率决定。…

传神论文中心|第11期人工智能领域论文推荐

在人工智能领域的快速发展中&#xff0c;我们不断看到令人振奋的技术进步和创新。近期&#xff0c;开放传神&#xff08;OpenCSG&#xff09;社区发现了一些值得关注的成就。传神社区本周也为对AI和大模型感兴趣的读者们提供了一些值得一读的研究工作的简要概述以及它们各自的论…

如何进行电子故障失效分析FA?

在电子主板生产的过程中&#xff0c;一般都会出现失效不良的主板&#xff0c;因为是因为各种各样的原因所导致的&#xff0c;比如短路&#xff0c;开路&#xff0c;本身元件的问题或者是认为操作不当等等所引起的。 所以在电子故障的分析中&#xff0c;需要考虑这些因素&#x…

5.5 业务流程和业务逻辑设计

一、引言 1.1 项目背景 经过上述的论述&#xff0c;我们讨论一下业务流程和业务逻辑设计&#xff0c;通过合理的业务流程设计和业务逻辑设计&#xff0c;可以提高用户的购物体验&#xff0c;降低用户的操作成本&#xff0c;并确保用户的购物行为符合平台的规则和要求。同时&a…

旅游网页(HTML+CSS+JS)

前言 本篇博客就不给大家讲解了&#xff0c;直接上代码 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;https://blog.csdn.net/2302_78381559/category_12644031.html?spm1001.2014.3001.5482https://blog.csdn.net/2302_78381559/catego…

Linux防火墙管理

计算机防火墙用于保护内部网络&#xff0c;主机和网络安全&#xff0c;有硬件防火墙和软件防火墙两种&#xff0c;软件主要是用对数据包进行分析过滤来保证软件层面安全。 此外还有根据对数据封包形式确定的分类方法&#xff0c; 如代理服务器&#xff0c;类似网关的形式监控整…

Mcgs 屏幕Modbus RTU通讯调试

目录 1. 设备窗口1.1 添加设备构件1.2 设备配置1.2.1 通用串口父设备配置1.2.2 设备0--ModbusRTU配置2. 设计用户窗口2.1 关联设备通道与实时数据库2.3 用户窗口3. 通信测试本文想要实现通过Modbus协议与Mcgs屏幕进行通信收发数据。在使用Mcgs屏幕进行Modbus通信时,一般Mcgs屏…

如何完美解决 sun.security.validator.ValidatorException: PKIX path building failed

如何完美解决 sun.security.validator.ValidatorException: PKIX path building failed 博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的…

一种改进盲解卷积算法在旋转机械故障诊断中的应用(MATLAB)

滚动轴承故障形成后&#xff0c;故障区与其他零部件表面接触将产生循环平稳的瞬态脉冲。由于受到系统传递函数、轴转频和环境噪声的干扰&#xff0c;故障脉冲特征受到大幅衰减&#xff0c;在测得信号中表现十分微弱甚至完全不可见。盲解卷积算法通过搜索一个最优的有限脉冲响应…

“面向绿色流域构建的生态处理技术创新与实践论坛”在成都召开

由中华环保联合会、福州大学、上海大学联合主办&#xff0c;中华环保联合会水环境治理专业委员会、福建省环境功能材料先进技术工程研究中心、上海大学环境与化学工程学院承办的“2024全国水科技大会暨技术装备成果展览会”于5月14日在成都世纪城国际会议中心隆重开幕。 期间&a…

Python 中 Selenium 的 send_keys() 函数

我们将介绍 Selenium Python 中的 send_keys() 函数并演示其用法。 任何应用程序在进入市场之前都需要经过一些测试。 应用程序应首先满足与其名称相关的所有要求。 我们应该全面测试应用程序&#xff0c;因为没有人能够预测给予应用程序的确切输入。 Python Selenium 可以帮…

新书速览|Autodesk Inventor 2024入门与案例实战:视频教学版

《Autodesk Inventor 2024入门与案例实战&#xff1a;视频教学版》 本书内容 《Autodesk Inventor 2024入门与案例实战&#xff1a;视频教学版》以Autodesk Inventor 2024为平台&#xff0c;重点介绍Autodesk Inventor 2024中文版的各种操作方法及其在工程设计领域的应用。《Au…

企业光纤专线和家用的区别

企业光纤专线与家用宽带之间的主要区别在于服务对象、技术特性、性能、成本以及服务等级。以下是一些关键差异&#xff1a; 服务对象&#xff1a; 企业光纤专线&#xff1a;专门为企业用户设计&#xff0c;通常需要提供营业执照作为申请条件&#xff0c;适用于需要稳定、高速和…

计算机组成原理之运算方法和运算器

文章目录 数据与文字的表示方法定点表示法机器码&#xff08;机器数&#xff09;原码 反码补码移码 浮点表示法尾数规格化 数据与文字的表示方法 定点表示法 机器码&#xff08;机器数&#xff09; 正数的原码、反码、补码一样&#xff0c;负数的原码、反码、补码的符号位均为…

Surface安装Windows和Ubuntu双系统方法(包括Ubuntu适配触控屏的方法)

这是一个目录0.0 前言让我们从一块砖头开始现在你有了能进入windows系统的surface并且想安装Ubuntu现在Ubuntu也有了再见 前言 之前我的Surface装上Ubuntu了好好的&#xff0c;能用&#xff0c;但是Ubuntu原本的内核是不支持很多Surface的功能的&#xff0c;比如触控屏&#xf…

SpringCloudAlibaba组件集成

SpringCloudAlibaba组件集成 Nacos服务注册与发现 1.Nacos认识与安装 1.1.什么是Nacos Nacos和Eureka有着相同的能力&#xff0c;甚至更为强大&#xff0c;作为Dubbo 生态系统中重要的注册中心实现。官方对它有如下定义&#xff1a; Nacos致力于帮助您发现&#xff0c;配置…

AI产品经理还不会数据挖掘❓看完这篇就够了

前言 在数字化时代的浪潮中&#xff0c;AI产品经理正成为推动科技与商业融合的重要力量。然而&#xff0c;面对海量的数据&#xff0c;如何从中挖掘出有价值的信息&#xff0c;为AI产品的开发提供有力支持&#xff1f;这已成为AI产品经理必须面对的挑战。今天&#xff0c;我们…