【C语言】函数(四):函数递归与迭代,二者有什么区别

news2024/9/24 11:24:29

目录

  • 前言
  • 递归
    • 定义
    • 递归的两个必要条件
    • 接受一个整型值(无符号),按照顺序打印它的每一位
    • 使用函数不允许创建临时变量,求字符串“abcd”的长度
    • 求n的阶乘
    • 求第n个斐波那契数
  • 迭代
  • 总结
  • 递归与迭代的主要区别
    • 用法不同
    • 结构不同
    • 时间开销不同
  • 两个经典问题

前言

从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事!故事是什么?“从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事!故事是什么?‘从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事!故事是什么?……’”

递归

定义

计算机科学中,递归是是指在函数的定义中使用函数自身的方法。它通常把一个大型的复杂问题层层转化为一个与原问题相似的规模较小的问题来求解,递归只需要少量的代码就可以描述出解题过程中的多次重复计算,大大减少了程序的代码量。
递归的主要思想是:大化小

递归的两个必要条件

1.存在限制条件,当满足这个限制条件时,递归停止。
2.每次递归调用之后越来越接近限制条件。

错误示例:

#include<stdio.h>

int main()
{
	printf("hello world!\n");
	main();
	return 0;
}

在这里插入图片描述

画红线的地方,意思是栈溢出,从上面写的程序中发现,递归没有停止的限制条件,导致死递归。

接受一个整型值(无符号),按照顺序打印它的每一位

示例1:
问题描述:
接受一个整型值(无符号),按照顺序打印它的每一位。
样例输入:1234
样例输出:1 2 3 4

代码示范:

#include<stdio.h>

void Func(unsigned int x)
{
	if (x > 9)
	{
		Func(x / 10);
	}
	printf("%d ", x % 10);
}
int main()
{
	unsigned int num = 0;
	scanf("%d", &num); //假设输入的是123
	Func(num);

	return 0;
}

在这里插入图片描述

到这里对递归应该有一个比较清晰的认识了,在图中红色过程表示的就是递归当中的“递”,蓝色过程表示的就是递归当中的“归”。

使用函数不允许创建临时变量,求字符串“abcd”的长度

示例2:
问题描述:
使用函数不允许创建临时变量,求字符串“abcd”的长度
分析: 直接求字符串“abcd”的长度,它是字符串,在前面文章中说过字符串的结束标志是 \0

代码展示:

#include<stdio.h>
#include<string.h>

int Length(char* l)
{
	int count = 0;
	while (*l != '\0')
	{
		count++;
		l++;
	}
	return count;
}
int main()
{
	char arr[] = "abcd";
	int len = Length(arr);
	printf("%d\n", len);
	return 0;
}

这段代码完全没有问题,但是题目要求不允许创建临时变量,这里创建了临时变量count

再分析:
在这里插入图片描述

所以我们可以将Length函数写成递归的形式:

//递归
int Length(char* length)
{
	if (*length == '\0')
		return 0;
	else
		return 1 + Length(length + 1);
}

我们再分析过程:

在这里插入图片描述

求n的阶乘

我们回顾一下n!怎么算:

#include<stdio.h>

int Func(int x)
{
	int i = 0;
	int j = 1;
	for (i = 1; i <= x; i++)
	{
		j *= i;
	}
	return j;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int result = Func(n);
	printf("%d\n", result);

	return 0;
}

上述代码是非递归的形式,我们再来思考一下如何使用递归来写n!
在这里插入图片描述

我们可以发现n!=n*(n-1)!
所以Func函数可以写成:

int Func(int x)
{
	if (x <= 1)
		return 1;
	else 
		return x * Func(x - 1);
}

求第n个斐波那契数

斐波那契数列由01开始,之后的斐波那契数就是由之前的两数相加而得出。
前几个斐波那契数是:
1、 1、 2、 3、 5、 8、 13、 21、 34、 55、 89、 144……
也就是说从第三个数开始,后面的每一个斐波那契数都是前两个数的和。
在这里插入图片描述

到这里就可以直接写代码了:

#include<stdio.h>

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 result = Fib(n);
	printf("%d\n", result);

	return 0;
}

这时候我们信心满满,让计算机帮我求第50个斐波那契数,当我们执行程序后在键盘输入50,却发现,等了很久都没有发现它输出内容。
为什么?

我们要求第50个斐波那契数,就需要计算第49个和第48个数,计算第49个数又需要计算第48个数和第47个数,可以想一下上面这个图画到末端需要画多少,除了前两个数,要算2的48次方,而int型只占4个字节的内存,也就是32位,2的32次方都已经42亿多,可想而知计算量非常庞大。按照递归的方式要进行大量的重复计算。我们可以做一个计数,计算第40个斐波那契数中3被计算了多少次,你会发现3被计算了将近四千万次,效率非常低。所以不是因为计算机偷懒没算,它也在拼命地计算,只是量太大,它一会儿也算不出来。

那么应该如何改进呢?

迭代

在计算机科学中,迭代是程序中对一组指令(或一定步骤)的重复。它既可以被用作通用的术语(与“重复”同义),也可以用来描述一种特定形式的具有可变状态的重复。可以简单理解为普通循环。但与普通循环有所差别,迭代时,循环代码中参与运算的变量同时是保存结果的变量,当前保存的结果作为下一次循环计算的初始值

我们知道函数形参是被存放在栈区当中,递归每“递”一次,就要开辟一个变量的内存,那么当参数非常大的时候,栈区内存不够了,栈区放不下了,也就是说栈区空间已经被耗尽了,但是你的东西还没放完,这个时候就会出现栈溢出的现象。

那么我们回头再看求第n个斐波那契数,能否将递归转换成迭代的形式。
在这里插入图片描述

	while (n >= 3)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	return c;

那么当n小于3的时候,也就是第1个或者第2个数,都是1,所以在给a,b,c初始化的时候,都赋值为1即可。

完整代码:

#include<stdio.h>
//迭代
int Fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 1;
	while (n >= 3)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	return c;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int result = Fib(n);
	printf("%d\n", result);

	return 0;
}

这个时候程序运行后输入50,尽管结果仍然是错误的,但是速度非常快。解决了大量重复计算的问题。

总结

当使用递归可能会导致栈溢出时,程序效率明显下降的时候,就不能够使用递归了。
如何解决?
1.可以使用迭代替换递归。
2.在递归函数设计中可以使用static限制变量,让变量申请一块内存后,在那一块内存折腾。不仅不再大量开辟栈区内存,从而导致栈溢出,并且static可以保存递归时的中间状态,并且为各个调用层所访问。

  • 递归代码量少,迭代不易想到,递归比迭代更清晰。所以许多问题采用递归的方式解释。
  • 迭代实现比递归实现的代码可读性差,但是效率高。
  • 当问题复杂的时候,难以用迭代实现,此时使用递归会更加简洁。

递归与迭代的主要区别

用法不同

  • 迭代是代码块的重复。虽然需要更多的代码,但时间复杂度通常小于递归的时间复杂度。
  • 递归是多次调用自身,因此代码长度非常小。但是,当有非常非常多次的递归调用时,递归的时间复杂度可能会呈指数级增长。

结构不同

  • 迭代是环结构,从初始状态开始,每次迭代都遍历这个环,并更新状态,多次迭代直到到达结束状态。
  • 递归是树结构,从字面可以理解为重复“递”和“归”的过程,当“递”到达底部时就会开始“归”。

时间开销不同

  • 与迭代相比,递归具有大量的开销。递归具有重复函数调用的开销,即由于重复调用同一函数,代码的时间复杂度增加了许多倍。

两个经典问题

  • 汉诺塔问题
  • 青蛙跳台阶问题

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

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

相关文章

【Python】实现一个简单的区块链系统

本文章利用 Python 实现一个简单的功能较为完善的区块链系统&#xff08;包括区块链结构、账户、钱包、转账&#xff09;&#xff0c;采用的共识机制是 POW。 一、区块与区块链结构 Block.py import hashlib from datetime import datetimeclass Block:"""区…

智能头盔天眼摄像头、单兵执法记录仪等配合MESH自组网在应急指挥调度中的应用

智能头盔、天眼摄像头、头盔记录仪、头盔摄像头、单兵执法记录仪等配合MESH自组网在应急指挥调度中的应用。 20人背负单兵自组网&#xff08;带手咪&#xff09;到训练场&#xff0c;戴头盔&#xff0c;头盔上放头盔式摄像头&#xff0c;大功率自组网设置在制高点&#xff0c;…

改进YOLOv8 | YOLOv5系列:RFAConv续作,即插即用具有任意采样形状和任意数目参数的卷积核AKCOnv

RFAConv续作,构建具有任意采样形状的卷积AKConv 一、论文yolov5加入的方式论文 源代码 一、论文 基于卷积运算的神经网络在深度学习领域取得了显著的成果,但标准卷积运算存在两个固有缺陷:一方面,卷积运算被限制在一个局部窗口,不能从其他位置捕获信息,并且其采样形状是…

五种多目标优化算法(MOJS、NSGA3、MOGWO、NSWOA、MOPSO)求解微电网多目标优化调度(MATLAB代码)

一、多目标优化算法简介 &#xff08;1&#xff09;多目标水母搜索算法MOJS 多目标优化算法&#xff1a;多目标水母搜索算法MOJS&#xff08;提供MATLAB代码&#xff09;_水母算法-CSDN博客 &#xff08;2&#xff09;NSGA3 NSGA-III求解微电网多目标优化调度&#xff08;M…

c语言数字转圈

数字转圈 题干输入整数 N&#xff08;1≤N≤9&#xff09;&#xff0c;输出如下 N 阶方阵。 若输入5显示如下方阵&#xff1a; * 1** 2** 3** 4** 5* *16**17**18**19** 6* *15**24**25**20** 7* *14**23**22**21** 8* *13**12**11**10** 9*输入样例3输出样例* 1*…

腾讯云云服务器旗舰新品SA5重磅首发

近日&#xff0c;腾讯云云服务器CVM再升级&#xff0c;极具性价比的云服务器旗舰新机型SA5重磅发布&#xff0c;搭载第四代AMD EPYC处理器&#xff08;Bergamo&#xff09;&#xff0c; 相比云服务器SA3实例&#xff0c;整机性能最大提升120%以上。 温馨提醒&#xff1a;购买腾…

【Java 进阶篇】Jedis 操作 String:Redis中的基础数据类型

在Redis中&#xff0c;String是最基础的数据类型之一&#xff0c;而Jedis作为Java开发者与Redis交互的利器&#xff0c;提供了丰富的API来操作String。本文将深入介绍Jedis如何操作Redis中的String类型数据&#xff0c;通过生动的代码示例和详细的解释&#xff0c;让你轻松掌握…

基于白鲸算法优化概率神经网络PNN的分类预测 - 附代码

基于白鲸算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于白鲸算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于白鲸优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

【电子通识】什么是物料清单BOM(Bill of Material))

BOM (Bill of Materials)是我们常说的物料清单。BOM是制造业管理的重点之一&#xff0c;用于记载产品组成所需要的全部物料&#xff08;Items&#xff09;。物料需求的计算是从最终产品开始&#xff0c;层层往下推算出部件&#xff0c;组件&#xff0c;零件和原材料的需求量。这…

MindStudio学习一 整体介绍

一场景介绍 二 安装介绍 1.LINUX 采用无昇腾硬件采用linux 分部署 2.WINDOWS 3.linux下安装整体步骤 3.1安装依赖 3.2 安装步骤 1.gcc cmake 等依赖 2.python3.7.5 3.pip 安装依赖 4.安装JDK 5.安装 Ascend-cann-toolkit 6.解压安装Mindstudio 7.进入bin路径 ./…

Cortex-M与RISC-V区别

环境 Cortex-M以STM32H750为代表&#xff0c;RISC-V以芯来为代表 RTOS版本为RT-Thread 4.1.1 寄存器 RISC-V 常用汇编 RISC-V 关于STORE x4, 4(sp)这种寄存器前面带数字的写法&#xff0c;其意思为将x4的值存入sp4这个地址&#xff0c;即前面的数字表示偏移的意思 反之LOA…

基于广义正态分布算法优化概率神经网络PNN的分类预测 - 附代码

基于广义正态分布算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于广义正态分布算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于广义正态分布优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xf…

【LM358AD运放方波振荡器可控输出幅值】2022-2-25

缘由仿真如何缩小方波振荡电路方波幅值?-有问必答-CSDN问答

再生式收音机踩坑记

下载《A Simple Regen Radio for Beginners》这篇文章也有好几年了&#xff0c;一直没有动手&#xff0c;上周末抽空做了一个&#xff0c;结果相当令人沮丧&#xff0c;一个台也收不到&#xff0c;用示波器测量三极管振荡波形&#xff0c;只有在调节再生电位器R2过程中&#xf…

构造命题公式的真值表

构造命题公式的真值表 1&#xff1a;实验类型&#xff1a;验证性2&#xff1a;实验目的&#xff1a;3&#xff1a;逻辑联结词的定义方法4&#xff1a;命题公式的表示方法5&#xff1a;【实验内容】 1&#xff1a;实验类型&#xff1a;验证性 2&#xff1a;实验目的&#xff1a…

C语言——从终端(键盘)将 5 个整数输入到数组 a 中,然后将 a 逆序复制到数组 b 中,并输出 b 中 各元素的值。

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int i;int a[5];int b[5];printf("输入5个整数&#xff1a;\n");for(i0;i<5;i){scanf("%d",&a[i]);}printf("数组b的元素值为&#xff1a;\n");for(i4;i>0;i--…

[原创](免改BIOS)使用Clover升级旧电脑-(高阶玩法)让固态硬盘内置Win11 PE启动系统

[简介] 常用网名: 猪头三 出生日期: 1981.XX.XXQQ: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、Delphi…

[工业自动化-25]:IDEC和泉RU2S-24D/RU4S-24D继电器的使用说明和接线方式

目录 一、外观 1.1 继电器整体&#xff1a; 1.2 继电器主体&#xff1a; 1.3 底座&#xff1a; 二、RU系列通用继电器介绍 2.1 总体 2.2 性能规格 2.3 锁存杆 2.4 信号定义与连线 - 2S系列 &#xff08;1&#xff09;24V输入 &#xff08;2&#xff09;第一路输出 …

系统优化软件Bitsum Process Lasso Pro v12.4,供大家学习研究参考

1、自动或手动调整进程优先级;将不需要抑制的进程添加到排除列表; 2、设置动态提升前台运行的进程/线程的优先级 3、设置进程黑名单,禁止无用进程(机制为启动即结束,而非拦截其启动)。 4、优化I/O优先级以及电源模式自动化。 5、ProBalance功能。翻译成中文是“进程平衡…

【TypeScript】常见数据结构与算法(二):链表

文章目录 链表结构&#xff08;LinkedList&#xff09;链表以及数组的缺点数组链表的优势 什么是链表?封装链表相关方法源码链表常见面试题237-删除链表中的节点206 - 反转链表 数组和链表的复杂度对比 链表结构&#xff08;LinkedList&#xff09; 链表以及数组的缺点 链表…