C语言 8 函数递归

目录

1. 递归是什么?

2.递归的限制条件

3. 递归举例1

4. 递归举例2

5.迭代

6. 递归举例3

拓展学习:


1. 递归是什么?

递归是学习C语⾔函数绕不开的⼀个话题,那什么是递归呢?
递归其实是⼀种解决问题的⽅法,在C语⾔中,递归就是函数⾃⼰调⽤⾃⼰。
上述就是⼀个简单的递归程序,只不过上⾯的递归只是为了演⽰递归的基本形式,不是为了解决问
题,代码最终也会陷⼊死递归,导致栈溢出(Stack overflow)。
1.1 递归的思想:
把⼀个⼤型复杂问题层层转化为⼀个与原问题相似,但规模较⼩的⼦问题来求解;直到⼦问题不能再被拆分,递归就结束了。所以递归的思考⽅式就是把⼤事化⼩的过程。
递归中的递就是递推的意思,归就是回归的意思。

2.递归的限制条件

递归在书写有2个必要条件:
递归存在限制条件,当满⾜这个限制条件的时候,递归便不再继续。
每次递归调⽤之后越来越接近这个限制条件。

3. 递归举例1

题⽬:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。
分析和代码实现
我们知道n的阶乘的公式: n =   n ∗ ( n − 1)!
这里就含有一个不断使用同一个算数方法的思想, 这样的思路就是把⼀个较⼤的问题,转换为⼀个与原问题相似,但规模较⼩的问题来求解的。
当 n==0 的时候,n的阶乘是1,其余n的阶乘都是可以通过公式计算。
n的阶乘的递归公式如下:
那我们就可以写出函数Fact求n的阶乘,假设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;
}

每一次函数调用都会向内存申请一块空间(运行时堆栈/函数栈帧空间),用来函数调用过程中的相关信息。

1.函数在调用过程中由于给每一次递归调用都分配了空间,所有调用不会发生混乱

2.函数递归不能是无条件的,这样会造成栈溢出

4. 递归举例2

输⼊⼀个整数m,按照顺序打印整数的每⼀位。
⽐如:
输⼊:1234 输出:1 2 3 4
输⼊:520 输出:5 2 0
分析和代码实现
如果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)
直到被打印的数字变成⼀位数的时候,就不需要再拆分,递归结束。

代码:

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一样,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;
}
上述代码是能够完成任务,并且效率是⽐递归的⽅式更好的。
事实上,我们看到的许多问题是以递归的形式进⾏解释的,这只是因为它⽐⾮递归的形式更加清晰,但是这些问题的迭代实现往往⽐递归实现效率更⾼。
当⼀个问题⾮常复杂,难以使⽤迭代的⽅式实现时,此时递归实现的简洁性便可以补偿它所带来的运⾏时开销。

6. 递归举例3

举例3:求第n个斐波那契数
我们也能举出更加极端的例⼦,就像计算第n个斐波那契数,是不适合使⽤递归求解的,但是斐波那契数的问题通过是使⽤递归的形式描述的,如下:
看到这公式,很容易诱导我们将代码写成递归的形式,如下所⽰:
int Fib(int n)
{
 if(n<=2)
 return 1;
 else
 return Fib(n-1)+Fib(n-2);
}
#include <stdio.h>
int main()
{
 int n = 0;
 scanf("%d", &n);
 int ret = Fib(n);
 printf("%d\n", ret); 
 return 0;
}
当我们n输⼊为50的时候,需要很⻓时间才能算出结果,这个计算所花费的时间,是我们很难接受的,这也说明递归的写法是非常低效的,那是为什么呢?
其实递归程序会不断的展开,在展开的过程中,我们很容易就能发现,在递归的过程中会有重复计
算,⽽且递归层次越深,冗余计算就会越多。
#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;
}

 

这⾥我们看到了,在计算第40个斐波那契数的时候,使⽤递归⽅式,第3个斐波那契数就被重复计算了39088169次,这些计算是⾮常冗余的。所以斐波那契数的计算,使⽤递归是⾮常不明智的,我们就得想迭代的⽅式解决。
我们知道斐波那契数的前2个数都1,然后前2个数相加就是第3个数,那么我们从前往后,从⼩到⼤计算就⾏了。
这样就有下⾯的代码:
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;
}

总结:迭代的⽅式去实现这个代码,效率就要⾼出很多了。 有时候,递归虽好,但是也会引⼊⼀些问题,所以适可⽽⽌就好。

拓展学习:

青蛙跳台阶问题

汉诺塔问题

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

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

相关文章

【Spring】Springmvc学习Ⅲ

# Spring&#xff4d;vc学习Ⅲ 文章目录 一、图书管理系统1. 功能1.1 登录前端接口前端代码后端接口后端代码 1.2 图书列表展示步骤:图书类代码mock数据代码控制层调用代码服务层代码&#xff08;存储除数据库中需要存储的数据&#xff09; 2. 分层控制2.1 三层架构2.2 代码重…

Softing dataFEED OPC Suite通过OPC UA标准加速数字化转型

数字化转型的关键在于成功将信息技术&#xff08;IT&#xff09;与运营技术&#xff08;OT&#xff09;相融合&#xff0c;例如将商业应用程序和服务器与可编程逻辑控制器&#xff08;PLC&#xff09;和设备传感器相融合&#xff0c;为此&#xff0c;各种设备和系统必须能够相互…

【Day1:JAVA导学】

目录 1、path环境变量2、Java背景介绍2.1 Java SE&#xff1a;2.2 Java ME&#xff1a;2.3 Java EE&#xff1a; 3、Java的跨平台性3.1 Java跨平台的原理&#xff1a; 4、Java开发程序的三个步骤5、JDK的组成和配置5.1 JDK的组成&#xff1a; 6、IDEA项目结构介绍7、Java关键字…

01 | 为什么需要消息队列?

哪些问题适合使用消息队列来解决&#xff1f; 1. 异步处理 2. 流量控制 使用消息队列隔离网关和后端服务&#xff0c;以达到流量控制和保护后端服务的目的。 3. 服务解耦 无论增加、减少下游系统或是下游系统需求如何变化&#xff0c;订单服务都无需做任何更改&#xff0c…

秋招算法——AcWing101——拦截导弹

文章目录 题目描述思路分析实现源码分析总结 题目描述 思路分析 目前是有一个笨办法&#xff0c;就是创建链表记录每一个最长下降子序列所对应的节点的链接&#xff0c;然后逐个记录所有结点的访问情况&#xff0c;直接所有节点都被访问过。这个方法不是很好&#xff0c;因为需…

工作玩手机监测识别摄像机

工作场所的员工玩手机已经成为了一种常见的现象&#xff0c;特别是在办公室、生产车间等地方。而这种现象不仅仅影响了员工的工作效率&#xff0c;还可能会对工作安全造成一定的隐患。为了监测和识别员工玩手机的情况&#xff0c;工作玩手机监测识别摄像机应运而生。工作玩手机…

不知摄像机网段IP地址?别担心,这里有解决之道

在数字化、智能化的今天&#xff0c;摄像机作为安全监控和日常记录的重要工具&#xff0c;其应用越来越广泛。然而&#xff0c;在实际使用中&#xff0c;我们可能会遇到一些问题&#xff0c;比如忘记了摄像机的网段IP地址&#xff0c;这往往会让我们感到头疼。那么&#xff0c;…

Hashmap详细解析,原理及使用方法分析

hashmap基本原理 根据的hashCode值存储数据。由数组链表组成的&#xff0c;Entnr数组是HashMap的主体&#xff0c;数组中每个元素是一个单向链表。链表则是1/1解哈希冲突而存在的。在lava8中&#xff0c;使用红黑树优化。当链表长度大于8并且元素个数大于64&#xff0c;转为红…

官宣!招商工作全面启动“2024南京智博会”众多企业踊跃报名

2024南京智博会&#xff0c;作为一场盛大的科技盛宴&#xff0c;经过多年的发展与沉淀&#xff0c;已经成功跻身国内顶尖的高新技术产品及解决方案的展示平台之列&#xff0c;成为了引领行业趋势的风向标。本届智博会不仅汇聚了众多知名科技企业&#xff0c;更展现了国内外最前…

Java扫盲

1.常见的代码结构&#xff1a; 转自知乎天马行空的程序猿

##19 序列与时间序列预测:运用RNN和LSTM在PyTorch中的实践

文章目录 前言时间序列预测的基本概念关键概念 RNN及其局限性LSTM网络的崛起用PyTorch进行时间序列预测准备数据集数据预处理创建数据加载器构建LSTM模型训练模型测试和评估模型结语 前言 随着数据的爆炸式增长&#xff0c;时间序列预测在多个领域内变得越来越重要。它能帮助我…

jenkins+docker实现前后端项目的自动化构建和容器部署

1、安装环境 centos 2、docker安装 yum install docker# 启动docker systemctl start docker 3、docker 安装jenkins 3.1 拉取jenkins镜像 docker pull jenkins/jenkins:latest-jdk8 3.2 启动jenkins容器 docker run -d --name jenkins -u root --privilegedtrue --res…

界面组件DevExpress Reporting v24.1预览版 - 拥有原生Angular报表查看器

DevExpress Reporting是.NET Framework下功能完善的报表平台&#xff0c;它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集&#xff0c;包括数据透视表、图表&#xff0c;因此您可以构建无与伦比、信息清晰的报表。 下一个主要更新(v24.1)将于6月初发布&#xff…

JWT -- 复盘

1、前言 1.1、Token流程 先来回顾一下利用 token 进行用户身份验证的流程&#xff1a; 客户端使用用户名和密码请求登录服务端收到请求&#xff0c;验证用户名和密码验证成功后&#xff0c;服务端会签发一个 token&#xff0c;再把这个 token 返回给客户端客户端收到 token 后…

Linux进程(一) -- 介绍进程

计算机的系统架构 用户部分 这是用户直接与计算机交互的部分&#xff0c;包括以下三种操作&#xff1a; 指令操作&#xff1a;用户通过命令行界面&#xff08;CLI&#xff09;输入指令来操作计算机。开发操作&#xff1a;开发人员编写和调试程序代码&#xff0c;与计算机系统…

[AWS] stepfunctions-local

本质是本地docker,只支持异步调用 run aws-stepfunctions-localdocker run -p 8083:8083 \ --mount type=bind,readonly,source=/path/MockConfigFile.json,destination=/home/StepFunctionsLocal/MockConfigFile.json \ -e SFN_MOCK_CONFIG="/home/StepFunctionsLocal/…

照明灯具十大排名都有哪些?市面上比较流行的十大护眼台灯品牌推荐

照明灯具十大排名都有哪些&#xff1f;护眼台灯排名当中靠前的主要有书客、飞利浦、松下等品牌。照明灯具作为家居与办公环境中不可或缺的元素&#xff0c;其品质与选择直接关系到人们的视觉健康与舒适度。本文将为大家揭示照明灯具的十大排名&#xff0c;让大家了解市场上最受…

【科学研究】 女性主义:网络中的性别歧视现象

::: block-1 “时问桫椤”是一个致力于为本科生到研究生教育阶段提供帮助的不太正式的公众号。我们旨在在大家感到困惑、痛苦或面临困难时伸出援手。通过总结广大研究生的经验&#xff0c;帮助大家尽早适应研究生生活&#xff0c;尽快了解科研的本质。祝一切顺利&#xff01;—…

识别AI论文生成内容,降低论文高AI率

AI写作工具能帮我们在短时间内高效生成一篇毕业论文、开通报告、文献综述、任务书、调研报告、期刊论文、课程论文等等&#xff0c;导致许多人开始使用AI写作工具作为撰写学术论文的辅助手段。而学术界为了杜绝此行为&#xff0c;开始使用AIGC检测系统来判断文章是由AI生成还是…

TL(TypeLetters)功能扩展#002:解放CPU。带打字练习软件原理分析

TL&#xff08;TypeLetters&#xff09;功能扩展#002&#xff1a; 解放CPU&#xff0c;带打字练习软件原理分析。 今天Type Letters时发现一个问题&#xff0c;TL的CPU占用达到了14%&#xff0c;按说一个小小的打字练习软件&#xff0c;不会有这么高的CPU占用率&#xff0c;是什…