[C语言]第十一节 函数递归一基础知识到高级技巧的全景探索

news2024/10/6 16:12:12

目录

11.1. 递归是什么?

11.1.1 递归的思想:

11.2 递归的限制条件

举例1:求n的阶乘

画图推演

举例2:顺序打印⼀个整数的每⼀位

画图推演

11.3. 递归与迭代

举例3:求第n个斐波那契数


11.1. 递归是什么?

递归其实是⼀种解决问题的⽅法,在C语⾔中,递归就是函数⾃⼰调⽤⾃⼰。
写⼀个史上最简单的C语⾔递归代码:
#include <stdio.h>
int main()
{
 printf("hehe\n");
 main();//main函数中⼜调⽤了main函数
 return 0;
}

代码最终也会陷⼊死递归,导致栈溢出(Stack overflow)

11.1.1 递归的思想:

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

11.2 递归的限制条件

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

举例1:求n的阶乘

⼀个正整数的阶乘(factorial)是所有⼩于及等于该数的正整数的积,并且0的阶乘为1。
⾃然数n的阶乘写作n!。
题⽬ :计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。
  分析和代码实现
我们知道n的阶乘的公式: n =   n ∗ ( n − 1)!
举例:
5 ! = 5 * 4 * 3 * 2 * 1
4 ! = 4 * 3 * 2 * 1
所以 : 5 ! = 5 * 4 !
n==0 的时候,n的阶乘是1,
其余n的阶乘都是可以通过公式计算

那我们就可以写出函数Fact求n的阶乘,假设Fact(n)就是求n的阶乘,那么Fact(n-1)就是求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);
	n = Fact( n);
	printf("%d",n);
	return 0;
}
运⾏结果(这⾥不考虑n太⼤的情况,n太⼤存在溢出)
假设n=1000时

 

我们会发现结果并不正确,因为n是整型,n太大会导致溢出 

画图推演

举例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 )
#include <stdio.h>
void Print(int n)
{
	if (n > 9)
	{
		Print(n / 10);
	}
	printf("%d ",n % 10);
}
int main()

{
	int n = 0;
	scanf("%d", &n);
	Print(n);
	return 0;
}

 

 

在这个解题的过程中,我们就是使⽤了⼤事化⼩的思路
把Print(1234) 打印1234每⼀位,拆解为⾸先Print(123)打印123的每⼀位,再打印得到的4
把Print(123) 打印123每⼀位,拆解为⾸先Print(12)打印12的每⼀位,再打印得到的3
直到Print打印的是⼀位数,直接打印就⾏。
画图推演

 

 

11.3. 递归与迭代

递归是⼀种很好的编程技巧,但是很多技巧⼀样,也是可能被误⽤的,就像举例1⼀样,看到推导的公 式,很容易就被写成递归的形式:
int Fact(int n)
{
 if(n==0)
     return 1;
 else
     return n*Fact(n-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;
}
上述代码是能够完成任务,并且效率是⽐递归的⽅式更好的。
事实上,我们看到的许多问题是以递归的形式进⾏解释的,这只是因为它⽐⾮递归的形式更加清晰, 但是这些问题的迭代实现往往⽐递归实现效率更⾼。
当⼀个问题⾮常复杂,难以使⽤迭代的⽅式实现时,此时递归实现的简洁性便可以补偿它所带来的运 ⾏时开销。

举例3:求第n个斐波那契数

我们也能举出更加极端的例⼦,就像计算第n个斐波那契数,是不适合使⽤递归求解的,但是斐波那契 数的问题通过是使⽤递归的形式描述的,如下:

 

看到这公式,很容易诱导我们将代码写成递归的形式,如下所⽰:
#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);
	n=Fib(n);
	printf("%d", n);
	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;
}
迭代的⽅式去实现这个代码,效率就要⾼出很多了。
有时候,递归虽好,但是也会引⼊⼀些问题,所以对于递归的使用,适可⽽⽌就好。

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

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

相关文章

oh-topic-editor: OpenHarmony HarmonyOS平台上基于RichEditor实现的支持添加话题、@用户的文本编辑组件

需求 在App开发中&#xff0c;我们常常会遇到发布文章、评论的时候需要添加话题或者用户的需求&#xff0c;就像微博那样。这在Android、iOS或者其他平台上都有现成的组件可供使用&#xff0c;但是HarmonyOS NEXT作为一个新兴平台&#xff0c;三方库实在匮乏&#xff0c;连微博…

SpringBoot中,接口签名,通用方案,以确保接口的安全性

1. 为什么需要接口签名&#xff1f; 接口签名目的&#xff1a;防止第三方伪造请求。请求伪造&#xff1a;未经授权的第三方构造合法用户的请求来执行不希望的操作。转账接口示例&#xff1a;展示了如果接口没有安全措施&#xff0c;第三方可以轻易伪造请求&#xff0c;例如将资…

用户在网页上输入一个网址,它整个页面响应的流程是什么?

目录 一、流程的大致过程 二、流程的详细分析 1. 浏览器先分析超链接中的URL 2. DNS解析 3. 建立TCP连接 建立连接&#xff08;三次握手&#xff09; HTTP中的请求报文 4. 浏览器发送HTTP请求 5. 服务器处理请求并发送响应 HTTP的响应报文 6. 浏览器接收响应 7. 渲…

After-kaoyan

知乎 - 安全中心 有态度&#xff0c;有回应&#xff0c;有温度&#xff0c;是跟双鱼相处的基础 我今天跟大家泄漏一个秘密&#xff0c;这个秘密也很简单&#xff0c;就是我每次遇到困难险阻时候我从不退缩&#xff0c;我也不会想着&#xff1a;“算了吧&#xff0c;我做不到&a…

基于Springboot+Vue的零食批发商仓库管理系统(含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 在这个…

Python调试技巧:高效定位与修复问题

Python调试技巧&#xff1a;高效定位与修复问题 在Python编程过程中&#xff0c;调试是不可避免的重要环节。无论是刚接触编程的初学者还是经验丰富的开发者&#xff0c;都可能会遇到代码运行不符合预期的情况。高效的调试技巧不仅能帮助我们快速找到问题&#xff0c;还能减少…

Graphiti:如何让构建知识图谱变得更快、更具动态性?

扩展大语言模型数据提取&#xff1a;挑战、设计决策与解决方案 Graphiti 是一个用于构建和查询动态、时间感知的知识图谱的 Python 库。它可以用于建模复杂、不断演变的数据集&#xff0c;并确保 AI 智能体能够访问它们完成非平凡任务所需的数据。它是一个强大的工具&#xff…

9个微服务最佳实践

1⃣分离数据存储&#xff1a;独立数据库&#xff0c;提升灵活性。 2⃣代码成熟度一致&#xff1a;质量稳定&#xff0c;避免技术债务 3⃣独立构建流程&#xff1a;独自构建&#xff0c;快速部署。 4⃣单一职责原则&#xff1a;业务功能单一&#xff0c;简化维护。 5⃣容器化部署…

Android车载——VehicleHal初始化(Android 11)

1 概述 VehicleHal是AOSP中车辆服务相关的hal层服务。它主要定义了与汽车硬件交互的标准化接口和属性管理&#xff0c;是一个独立的进程。 2 进程启动 VehicleHal相关代码在源码树中的hardware/interfaces/automotive目录下 首先看下Android.bp文件&#xff1a; cc_binary …

大模型公司对标:360

公司档案 360成立于2005年&#xff0c;初期以提供免费的杀毒软件“360安全卫士”而迅速获得市场认可&#xff0c;并逐渐发展成为一家提供全面互联网安全解决方案的企业。2015年成立人工智能研究院&#xff0c;开展人工智能技术探索&#xff0c;成为国内布局研究开发人工智能较…

Oracle 表空间异构传输

已经有了表空间的数据文件&#xff0c;和元数据dump文件&#xff0c;如何把这个表空间传输到异构表空间中&#xff1f; 查询异构传输平台信息&#xff1a; COLUMN PLATFORM_NAME FORMAT A40 SELECT PLATFORM_ID, PLATFORM_NAME, ENDIAN_FORMAT FROM V$TRANSPORTABLE_PLATFORM O…

教育技术革新:SpringBoot在线教育系统开发指南

6系统测试 6.1概念和意义 测试的定义&#xff1a;程序测试是为了发现错误而执行程序的过程。测试(Testing)的任务与目的可以描述为&#xff1a; 目的&#xff1a;发现程序的错误&#xff1b; 任务&#xff1a;通过在计算机上执行程序&#xff0c;暴露程序中潜在的错误。 另一个…

计算机找不到vcomp140.dll,无法继续执行代码如何解决,有什么好的修复方法

1. vcomp140.dll 简介 1.1 定义 vcomp140.dll 是一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;它属于 Microsoft Visual C 2015 Redistributable Package 的一部分。该文件为应用程序提供了 OpenMP 并行框架所需的运行时支持&#xff0c;允许开发者编写并发和多…

【Verilog学习日常】—牛客网刷题—Verilog进阶挑战—VL25

输入序列连续的序列检测 描述 请编写一个序列检测模块&#xff0c;检测输入信号a是否满足01110001序列&#xff0c;当信号满足该序列&#xff0c;给出指示信号match。 模块的接口信号图如下&#xff1a; 模块的时序图如下&#xff1a; 请使用Verilog HDL实现以上功能&#x…

论文笔记:微表情欺骗检测

整理了AAAI2018 Deception Detection in Videos 论文的阅读笔记 背景模型实验可视化 背景 欺骗在我们的日常生活中很常见。一些谎言是无害的&#xff0c;而另一些谎言可能会产生严重的后果。例如&#xff0c;在法庭上撒谎可能会影响司法公正&#xff0c;让有罪的被告逍遥法外。…

电脑获得高级管理员权限(Windows10 专业版)

电脑获得高级管理员权限(Windows10 专业版) 请谨慎操作 通常我们在删除一些文件时&#xff0c;会提示权限不足&#xff0c;删除不了文件 我们可以打开组策略编辑器将当前用户修改为高级管理员权限 Windows10获取高级管理员权限 首先打开本地组策略编辑器(cmd输入gpedit.msc)其…

20分钟写一个链表

目录 前言1.带头结点的循环双链表1.1 链表的分类、线性表的对比1.2 双链表基本操作代码实现1.2.1 初始化1.2.2 销毁、打印链表 总结 前言 有一个学长在面试的时候被问到这样一个问题&#xff0c;“你可以用20分钟写一个链表吗&#xff1f;”学长第一反应是&#xff0c;至少要一…

传统图像处理Opencv分割不同颜色的夹子

任务要求&#x1f349; 1. 计算图像中夹子的总数。 2. 分别计算不同颜色夹子的个数。 3. 使用以下方法适应三张图片&#xff0c;并在每张图像上显示结果&#xff1a; - 阈值方法 - HSV颜色空间 - 连通域分析 - 形态学图像处理 - Canny边缘检测 4. 在结果中显示计…

北交大研究突破:塑料光纤赋能低成本无摄像头AR/VR眼动追踪技术

北交大研究&#xff1a;探索无摄像头低成本AR/VR眼动追踪新路径 在AR/VR技术领域&#xff0c;眼动追踪作为一项关键技术&#xff0c;对于提升用户体验、优化渲染效率具有重要意义。然而&#xff0c;传统的眼动追踪方案多依赖于高成本的摄像头&#xff0c;这不仅增加了设备的制造…

学习资料库系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;观看记录管理&#xff0c;基础数据管理&#xff0c;论坛信息管理&#xff0c;公告信息管理&#xff0c;轮播图信息 微信端账号功能包括&#xff1a;系统首页&#xff0c;阅读资…