函数递归与迭代

目录

1.递归

1.1递归的思想

1.2递归的限制条件

2.递归与迭代


1.递归

  • 函数递归是什么?

    递归是学习C语⾔函数绕不开的⼀个话题,那什么是递归呢?

    递归其实是⼀种解决问题的⽅法,在C语⾔中,递归就是函数⾃⼰调⽤⾃⼰。

写⼀个史上最简单的C语⾔递归代码:

#include <stdio.h>
int main()
{printf("hehe\n");main();//main函数中⼜调⽤了main函数 死循环不断的打印hehereturn 0;
}
  • 每一次函数调用,都要为这次函数调用分配内存空间是内存的栈区上分配的,如果无限的递归调用函数,就会将栈区空间填满(使用完) ,这时就出现了栈溢出(Stack flow)的现象

1.1递归的思想

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

递归中的递就是递推的意思,归就是回归的意思,接下来慢慢来体会

1.2递归的限制条件

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

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

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

在下面的例⼦中,我们逐步体会这2个限制条件

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

当 n==0 的时候,n的阶乘是1,其余n的阶乘都是可以通过公式计算。

n的阶乘的递归公式如下:

  • 递归就是:递推,回归的意思,一开始先一步步的递推下去,然后再回归将结果一个个计算得到最终结果

  • 少量的代码完成复杂的运算(递归不断的申请内存空间如果不及时结束的话就会溢出)

int Fact(int n) {   if (n == 0) {  //n=0限制条件结束条件return 1; // n=0的时候不再往下拆解  达到限制条件}else if (n > 0){return n * Fact(n - 1);}else{printf("输入的值有误请重新输入:\n");}
​}
int main() {int n = 0;scanf("%d", &n);int ret = Fact(n);printf("%d", ret);return 0;
}
  • 输⼊⼀个整数m,按照顺序打印整数的每⼀位。

  • 分析:用递归的方法 我们将 1234 按顺序输出 1 2 3 4

    我们可以定义一个Print()函数

    先递推:(一直递推到最高位,然后再从最高位开始打印,就会按顺序输出)

    (1234) 除以十去掉最后一位 (123) 4

    (123) 4 ---> (12) 3 4 ----> (1)2 3 4 ---> 1 2 3 4

    每次都调用自己,直到不能再分(限制条件)

    后回归:

  • 最后当n=1的时候不满足n>9的条件,达到限制条件然后进行回归,

    1%10 = 1

    12%10=2

    123%10 =3

    然后再顺序输出1 2 3

 

int Print(int n) {if (n > 9)//当n是两位数以上{Print(n / 10);}printf("%d ", n % 10);
}
int main() {int n = 0;scanf("%d", &n);Print(n);}

总结:就是写一个限制条件,然后不断的调用自己本身(递推过程),当达到限制条件的时候停止递推,然后回归(不断返回)

2.递归与迭代

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

int Fact(int n)
{if(n==0)return 1;elsereturn n*Fact(n-1)
}

Fact函数是可以产⽣正确的结果,但是在递归函数调⽤的过程中涉及⼀些运⾏时的开销。

在C语⾔中每⼀次函数调⽤,都需要为本次函数调⽤在内存的栈区,申请⼀块内存空间来保存函数调⽤期间的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧。

涵数不返回,函数对应的栈帧空间就⼀直占用,所以如果函数调用中存在递归调用的话,每⼀次递归函数调用都会开辟属于自己的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间

所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(stack overflow)的问题

  • 我们可以用另外一种更简便的方法,迭代(循环中的一种)

//循环(其中一种循环是迭代)
int Fact(int n) {int ret = 1; //记得从1开始 如果0的话每次相乘都为0int i = 0;for (i = 1; i <= n; i++) {ret *= i;}return ret;
}
int main() {int n = 0;scanf("%d", &n);int r = Fact(n);printf("%d\n", r);return 0;
}

我们怎么权衡递归和迭代呢?

  • 如果是一个非常复杂的问题,我们可以用简单的递归方法解决,又不会造成栈溢出就使用递归调用

  • 如果用递归方法出现很明显的缺陷,造成溢出;就是用迭代的方法

比如:求斐波那契数列,用递归的方法计算,

这个代码当计算很大的数字的时候,速度非常慢,有大量重复的计算

//求斐波拉契
int Fib(int n) {if (n <= 2)return 1;elsereturn Fib(n - 1) + Fib(n - 2);
}
int main() {int n = 0;scanf("%d", &n);int r = Fib(n);printf("%d", r);return 0;
}

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

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 r = Fib(n);printf("%d\n", r);printf("count=%d", 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--;//每次剪到2的时候就退出循环}return c;
}
int main() {int n = 0;scanf("%d", &n);int r = Fib(n);printf("%d\n", r);return 0;
}
  • 有个递归的例子(递归层次太深),会死递归;溢出 等他一直加到3989的时候就会溢出

  • 上面斐波拉契函数递归层次不会太深,但是次数冗余

void test(int n) {printf("%d ", n);if (n <= 10000) {test(n + 1);  }
}
​
int main() {test(1);return 0;
}

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

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

相关文章

代码随想录算法训练营第五十三天| 1143.最长公共子序列 ,1035.不相交的线,53. 最大子序和 动态规划

题目与题解 1143.最长公共子序列 题目链接&#xff1a;1143.最长公共子序列 代码随想录题解&#xff1a;​​​​​​​1143.最长公共子序列 视频讲解&#xff1a;动态规划子序列问题经典题目 | LeetCode&#xff1a;1143.最长公共子序列_哔哩哔哩_bilibili 解题思路&#xff…

Linux Makefile编写之可执行程序

1 概述 编译工具有很多(make/cmake/BJam)。如果不考虑跨平台的话&#xff0c;还是make比较方便。使用make编译需要编写Makefile。本文编写Makefile来生成C/C可执行程序。 2 Makefile文件命名 Makefile文件首先是一个文本文件&#xff0c;Linux下默认有两种命名方式: Makefil…

DBSCAN算法学习

DBSCAN算法 文章目录 DBSCAN算法概述应用场景优缺点基于sklearn库的样例DBSCAN、分层聚类和K均值聚类比较 概述 DBSCAN算法是一种基于密度的聚类算法&#xff0c;能够自动识别不同的簇&#xff0c;并与噪声数据分开。以下是关于DBSCAN算法的重要知识点概述&#xff1a; 基本概…

vue3中如何父组件中使用弹框,子组件中关闭弹框

子组件: <template><el-dialogv-model"visible"title"Tips"width"500"><div class"left"></div><div class"right"></div><template #footer><div class"dialog-footer…

Learning to Upsample by Learning to Sample

摘要 论文&#xff1a;https://arxiv.org/pdf/2308.15085 我们提出了DySample&#xff0c;一个超轻量级且高效的动态上采样器。虽然最近的基于内核的动态上采样器&#xff0c;如CARAFE、FADE和SAPA&#xff0c;取得了令人印象深刻的性能提升&#xff0c;但它们引入了大量的计算…

前端实现文件下载的方法

一、简介 ​ 之前我分享过《前端实现图片下载的方法》&#xff0c;但那只是针对图片下载的方法。本博客分享的是对于文件的下载方法&#xff0c;包括图片文件和非图片文件的下载&#xff0c;例如png、doc、pdf、ppt等等。 ​ 当然&#xff0c;还是那个大前提&#xff1a;在任…

大模型对数字营销的驱动赋能

一、大模型驱动的营销数智化个信未来发展趋势 1.模型算法能力全面升级 大模型凭借智能化的用户洞察&#xff0c;个性化的需求预测、系统化的数据分析、效率化的营销决策以及实实化的全域检测支持&#xff0c;为营销行业更加准确地把握市场动态和消费者需求提供了强大支持。可以…

Spring Boot 如何实现缓存预热

Spring Boot 实现缓存预热 1、使用启动监听事件实现缓存预热。2、使用 PostConstruct 注解实现缓存预热。3、使用 CommandLineRunner 或 ApplicationRunner 实现缓存预热。4、通过实现 InitializingBean 接口&#xff0c;并重写 afterPropertiesSet 方法实现缓存预热。 1、使用…

数据结构和算法:贪心

贪心算法 贪心算法是一种常见的解决优化问题的算法&#xff0c;其基本思想是在问题的每个决策阶段&#xff0c;都选择当前看起来最优的选择&#xff0c;即贪心地做出局部最优的决策&#xff0c;以期获得全局最优解。 贪心算法和动态规划都常用于解决优化问题。它们之间存在一…

TCP/IP协议族中的TCP(二):解析其关键特性与机制

⭐小白苦学IT的博客主页⭐ ⭐初学者必看&#xff1a;Linux操作系统入门⭐ ⭐代码仓库&#xff1a;Linux代码仓库⭐ ❤关注我一起讨论和学习Linux系统 滑动窗口 在前面我们讨论了确认应答策略, 对每一个发送的数据段, 都要给一个ACK确认应答. 收到ACK后再发送下一个数据段.这样…

力扣HOT100 - 98. 验证二叉搜索树

解题思路&#xff1a; class Solution {public boolean isValidBST(TreeNode root) {return recur(root,Long.MIN_VALUE,Long.MAX_VALUE);}public boolean recur(TreeNode root,long lower,long upper){if(rootnull) return true;if(root.val<lower||root.val>upper) re…

HTTP Host 头攻击 原理以及修复方法

漏洞名称 &#xff1a;HTTP Host头攻击 漏洞描述&#xff1a; 一般通用web程序是如果想知道网站域名不是一件简单的事情&#xff0c;如果用一个固定的URI来作为域名会有各种麻烦。开发人员一般是依赖HTTP Host header&#xff08;比如在php里_SERVER["HTTP_HOST"] …

Ubuntu上的screenfetch

2024年4月28日&#xff0c;周日下午 这些文本是由一个叫做 “screenfetch” 的命令生成的&#xff0c;它会显示一些系统和用户信息&#xff0c;包括操作系统、内核版本、系统运行时间、安装的软件包数量、使用的Shell、分辨率、桌面环境、窗口管理器、主题、图标主题、字体、CP…

K8s: 应用项目部署运维环境搭建

使用 StatefulSet 部署 Mysql 数据库环境准备是应用的前置准备工作 先在 node 节点上安装 mysql $ sudo yum install mysql-server -y 安装$ sudo systemctl start mysqld 启动$ sudo systemctl enable mysqld 设置开启启动$ sudo mysql_secure_installation 设置安全选项$ my…

Matlab进阶绘图第51期—带填充等高线的三维特征渲染散点图

带填充等高线的三维特征渲染散点图是填充等高线图与特征渲染三维散点图的组合。 其中&#xff0c;填充等高线图与特征渲染的三维散点图的颜色用于表示同一个特征。 由于填充等高线图无遮挡但不直观&#xff0c;特征渲染的三维散点图直观但有遮挡&#xff0c;而将二者组合&…

MySQL数据库进阶篇二(优化、视图/存储过程/存储函数/触发器)

目录 一、SQL优化1.1、插入数据1.2、主键优化1.3、order by优化1.4、group by优化1.5、limit优化1.6、count优化1.7、update优化 二、视图/存储过程/存储函数/触发器2.1、视图2.2、存储过程2.3、存储函数2.4、触发器 一、SQL优化 分为&#xff1a;插入数据优化&#xff0c;主键…

一文了解——企业网站为什么需要安装SSL证书 !

企业网站安装SSL证书主要是出于以下几个关键原因&#xff1a; 1. 数据加密&#xff1a;SSL证书能确保网站与用户浏览器之间的数据传输是加密的&#xff0c;保护敏感信息&#xff08;如登录凭据、个人信息、交易数据&#xff09;不被第三方截取或篡改&#xff0c;维护用户隐私安…

Apache Flink:流式数据处理的新典范

在大数据处理领域&#xff0c;Apache Flink以其强大的流式数据处理能力&#xff0c;逐渐成为了业界的新宠。Flink是一个分布式流处理框架&#xff0c;能够处理无界和有界数据流&#xff0c;提供了高吞吐、低延迟的数据处理能力。 Flink的核心优势在于其流处理和批处理的统一模…

968.监控二叉树 树上最小支配集

法一: 动态规划 一个被支配的节点只会有三种状态 1.它本身有摄像头 2.他没有摄像头, 但是它的父节点有摄像头 3.他没有摄像头, 但是它的子节点有摄像头 我们 dfs(node,state) 记录在node节点时(以node为根的子树),状态为state下的所有最小摄像头 // 本身有摄像头就看左右孩子…

Elementplus远程搜索下拉

远程搜索 :remote-method“getAppNumberList” <div class"filter-item"><span>型号:</span><el-select v-model"listQuery.numberId" clearable filterable :remote-method"getAppNumberList" remote placeholder"请…