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

目录

  • 前言
  • 递归
    • 定义
    • 递归的两个必要条件
    • 接受一个整型值(无符号),按照顺序打印它的每一位
    • 使用函数不允许创建临时变量,求字符串“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可以保存递归时的中间状态,并且为各个调用层所访问。

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

递归与迭代的主要区别

用法不同

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

结构不同

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

时间开销不同

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

两个经典问题

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

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

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

相关文章

Python基础:JSON保存结构化数据(详解)

1. JSON概念 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人阅读和编写&#xff0c;也易于机器解析和生产。   虽然JSON使用JavaScript语法来描述数据对象&#xff0c;但是JSON仍然独立于语言和平台&#xff0c;JSON解…

php获取当前域名方法

使用$_SERVER[HTTP_HOST]变量只获取到域名&#xff1a; $domain $_SERVER[HTTP_HOST]; echo $domain; 获取包含协议和域名的完整URL $protocol isset($_SERVER[HTTPS]) && $_SERVER[HTTPS] on ? https:// : http://; $domain $_SERVER[HTTP_HOST]; $current_url…

PyQt6库和工具库QTDesigner安装与配置

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计12条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…

【华为网络-配置-021】- MSTP 多实例配置及安全保护等

要求&#xff1a; 1、vlan 10 从红色链路转发。 2、vlan 20 从黄色链路转发。 一、基础配置 [SW1]vlan batch 10 20 [SW1]interface GigabitEthernet 0/0/1 [SW1-GigabitEthernet0/0/1]port link-type trunk [SW1-GigabitEthernet0/0/1]port trunk allow-pass vlan all [SW…

Ubuntu下使用protoBuf

一、protobuf简介&#xff1a; 1.1 protobuf的定义&#xff1a; protobuf是用来干嘛的&#xff1f; protobuf是一种用于 对结构数据进行序列化的工具&#xff0c;从而实现 数据存储和交换。 &#xff08;主要用于网络通信中 收发两端进行消息交互。所谓的“结构数据”是指类…

工具柜与6S管理的协同优势

在现代企业管理中&#xff0c;物料管理是确保生产流程高效、成本控制有效的关键环节。为了更好地实现物料管理目标&#xff0c;企业需要借助先进的技术支持。本文将聚焦于物料管理所需的技术支持&#xff0c;特别关注工具柜与6S管理的协同优势&#xff0c;为企业管理者提供实用…

2023年亚太杯数学建模A题水果采摘机器人的图像识别功能(免费思路)

中国是世界上最大的苹果生产国&#xff0c;年产量约为 3500 万吨。同时&#xff0c;中国也是世界上最大的苹果出口国&#xff0c;世界上每两个苹果中就有一个出口到国。世界上每两个苹果中就有一个来自中国&#xff0c;中国出口的苹果占全球出口量的六分之一以上。来自中国。中…

0002Java程序设计-springboot在线考试系统小程序

文章目录 **摘 要****目录**系统实现开发环境 编程技术交流、源码分享、模板分享、网课分享 企鹅&#x1f427;裙&#xff1a;776871563 摘 要 本毕业设计的内容是设计并且实现一个基于springboot的在线考试系统小程序。它是在Windows下&#xff0c;以MYSQL为数据库开发平台&…

react的开发中关于图片的知识

React是一个流行的JavaScript库&#xff0c;用于构建用户界面。在React开发中&#xff0c;图片是一个非常重要的元素&#xff0c;可以用于美化界面和展示内容。本篇博客将详细讲解React中关于图片的知识。 1. React中使用图片 在React中使用图片非常简单&#xff0c;只需要使…

React UI界面:Ant Design初步

文章目录 初步回调函数变量输出 React初步 初步 Antd是一套非常现代的React组件库&#xff0c;是好多人用过的第一个组件库&#xff0c;但我对其印象最深的却是圣诞节彩蛋&#xff0c;只是上网一查才发现&#xff0c;一晃这么多年过去了。 先创建一个React项目&#xff0c;然…

Leetcode刷题笔记题解(C++):1008. 前序遍历构造二叉搜索树

思路&#xff1a; 1.树中的第一个值为根&#xff08;数组的第一个值&#xff09;&#xff0c;小于根的值存放在左子树中&#xff0c;大于根的值存放在右子树中&#xff1b; 2.利用递归对左右子树 /*** Definition for a binary tree node.* struct TreeNode {* int val;*…

Android设计模式--享元模式

水不激不跃&#xff0c;人不激不奋 一&#xff0c;定义 使用共享对象可有效地支持大量的细粒度的对象 享元模式是对象池的一种实现&#xff0c;用来尽可能减少内存使用量&#xff0c;它适合用于可能存在大量重复对象的场景&#xff0c;来缓存可共享的对象&#xff0c;达到对象…

认识Linux操作系统

什么是操作系统&#xff1f; 操作系统是一款软硬件资源管理的软件Linux是一款具体的操作系统的品类&#xff08;Linux内核是用C语言写的&#xff09;centos7是一款具体的Linux操作系统 为什么要有操作系统&#xff1f; Linux操作系统 Linux是一种自由和开放源代码的类UNIX操…

Redis常用操作及应用(一)

一、五种数据结构 二、String结构 1、字符串常用操作 SET key value //存入字符串键值对 MSET key value [key value ...] //批量存储字符串键值对 SETNX key value //存入一个不存在的字符串键值对 GET key //获取一个字符串键值 MGET key [ke…

使用Pytorch从零开始构建Conditional PixelCNN

条件 PixelCNN PixelCNN 是 PixelRNN 的卷积版本&#xff0c;它将图像中的像素视为一个序列&#xff0c;并在看到前面的像素后预测每个像素&#xff08;定义如上和左&#xff0c;尽管这是任意的&#xff09;。PixelRNN 是图像联合先验分布的自回归模型&#xff1a; p ( x ) …

命令执行总结

之前做了一大堆的题目 都没有进行总结 现在来总结一下命令执行 我遇到的内容 这里我打算按照过滤进行总结 依据我做过的题目 过滤system 下面是一些常见的命令执行内容 system() passthru() exec() shell_exec() popen() proc_open() pcntl_exec() 反引号 同shell_exec() …

【从删库到跑路 | MySQL总结篇】数据库基础(增删改查的基本操作)

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【MySQL学习专栏】&#x1f388; 本专栏旨在分享学习MySQL的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 重点放前面&am…

通过ros系统中websocket中发送sensor_msgs::Image数据给web端显示(二)

通过ros系统中websocket中发送sensor_msgs::Image数据给web端显示(二) mp4媒体流数据 #include <ros/ros.h> #include <signal.h> #include <sensor_msgs/Image.h> #include <message_filters/subscriber.h> #include <message_filters/synchroniz…

计算机组成原理。3-408

1.动态存储和静态存储 2.双端口RAM 注意&#xff1a;cpu通过地址线和数据线读写数据时&#xff0c;不能同时写&#xff0c;但可以同时读&#xff0c;也不能一边读一边写。 3.多体并行存储器 分为高位存储和低位存储 小结 4.磁盘存储器的组成 5.磁盘的性能指标 磁盘读写寻道…

ddns-go部署在linux虚拟机

ddns-go部署ubuntu1804 1.二进制部署 1.虚拟机部署 1.下载linux的x86二进制包 wget https://github.com/jeessy2/ddns-go/releases/download/v5.6.3/ddns-go_5.6.3_linux_x86_64.tar.gz2.解压 tar -xzf ddns-go_5.6.3_linux_x86_64.tar.gz3.拷贝执行文件到PATH下&#xff0c…