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;elsereturn n*Fact(n-1);
}
#include <stdio.h>
int Fact(int n)
{if(n==0)return 1;elsereturn 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;elsereturn 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;elsereturn 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;
}

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

拓展学习:

青蛙跳台阶问题

汉诺塔问题

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024全新小狐狸AI免授权源码

源码安装说明&#xff1a; 下 载 地 址 &#xff1a; runruncode.com/php/19757.html 1. 在宝塔新建一个站点&#xff0c;选择 PHP 版本为 7.2、7.3 或 7.4。将压缩包上传到站点的根目录&#xff0c;并设置运行目录为 /public。 2. 导入数据库文件&#xff0c;该文件位于 …

电脑D盘XLS文件怎么恢复?6个恢复方法,找回丢失的文件!

在日常工作和学习中&#xff0c;我们经常会使用到XLS文件&#xff0c;也就是Excel表格文件。然而有时由于误操作或其他原因&#xff0c;我们可能会发现电脑D盘中的XLS文件突然不见了。这时如何快速、有效地恢复这些文件&#xff0c;成为了我们待解决的问题。本文将为大家介绍五…

Android 异常开机半屏重启代码分析

Android 的稳定性是 Android 性能的一个重要指标&#xff0c;它也是 App 质量构建体系中最基本和最关键的一环&#xff1b;如果应用经常崩溃&#xff0c;或者关键功能不可用&#xff0c;那显然会对我们的留存产生重大影响所以为了保障应用的稳定性&#xff0c;我们首先应该树立…

【JavaEE 初阶(六)】网络编程

❣博主主页: 33的博客❣ ▶️文章专栏分类:JavaEE◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多网络知识 目录 1.前言2.浅谈网络2.1基本知识2.2.OSI与TCP/IP 3.网络编程3.1TCP与UDP区别3.2UDP网路编程…

webpack生成模块关系依赖图示例:

npm i -D webpack-bundle-analyzer core-js babel-loaderwebpack.config.js const BundleAnalyzerPlugin require(webpack-bundle-analyzer).BundleAnalyzerPlugin; module.exports {entry: ./src/index.js,output: {filename: main.js,},// mode: production, // 或者 produ…

圈子系统开发,圈子系统源码, 圈子论坛系统,兴趣社区圈子论坛,一站式搭建,前端uniapp,后端think PHP

简述 圈子论坛社区系统&#xff0c;含完整的后台PHP系统。功能&#xff1a;小程序授权登陆&#xff0c;H5和APP&#xff0c;手机号登陆&#xff0c;发帖&#xff0c;建圈子、发活动。圈主可置顶推荐帖子&#xff0c;关注、点赞、评论、交流等。可作为圈子贴吧等自媒体。 兴趣…

gradle 共享存储挂载缓存目录的问题

2个任务同时构建的时候&#xff0c;报错如上。 原因&#xff1a;挂载目录的问题导致的&#xff0c;挂在最小粒度的目录下。 /home/app/.gradle/caches/modules-2/files-2.1 挂载到这个级别的目录下。

AR项目开发框架及特点

AR项目开发框架是指用于构建AR应用程序的软件平台。它们通常提供一组API和工具&#xff0c;用于创建3D模型、跟踪设备位置、并将虚拟对象叠加到现实世界中。AR项目开发框架被广泛应用于各种应用&#xff0c;例如游戏、教育、购物、导航和协作等。北京木奇移动技术有限公司&…

嵌入式和单片机的区别在哪?

嵌入式和单片机是两个不同的概念&#xff0c;它们在很多方面都存在着差异。嵌入式系统是一种专用的计算机系统&#xff0c;通常用于控制和监测其他设备。它通常由微处理器、存储器、输入/输出接口和其他外围设备组成。嵌入式系统可以运行各种操作系统&#xff0c;如 Linux、Win…

BGP基础配置实验

BGP基础配置实验 一、实验拓扑 初始拓扑&#xff1a; 最终拓扑&#xff1a; 二、实验要求及分析 实验要求&#xff1a; 1&#xff0c;R1为AS 100区域&#xff1b;R2、R3、R4为AS 200区域且属于OSPF协议&#xff1b;R5为AS 300区域&#xff1b; 2&#xff0c;每个设备上都有…

基于阿里云向量检索 Milvus 版与 PAI 搭建高效的检索增强生成(RAG)系统

阿里云向量检索 Milvus 版现已无缝集成于阿里云 PAI 平台&#xff0c;一站式赋能用户构建高性能的检索增强生成&#xff08;RAG&#xff09;系统。您可以利用 Milvus 作为向量数据的实时存储与检索核心&#xff0c;高效结合 PAI 和 LangChain 技术栈&#xff0c;实现从理论到实…

哪个品牌led灯好?五大好用护眼台灯推荐

哪个品牌led灯好&#xff1f;目前LED护眼台灯当中做得比较好的有明基、松下、书客等品牌。在如今LED灯市场的海洋中&#xff0c;选择一款可靠的护眼台灯变得愈发重要。然而&#xff0c;众多品牌和产品的涌现也让消费者面临着选择困难。为了帮助大家找到最合适的LED台灯&#xf…