C——函数递归

在 C 语言里,函数递归是一种函数调用自身的编程技术。下面开始逐一介绍。

一、什么是递归?

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

写⼀个史上最简单的C语⾔递归代码:
#include <stdio.h>
int main()
{
    printf("hehe\n");
    main();//main函数中⼜调⽤了main函数
    return 0;
}
上述就是⼀个简单的递归程序,只不过上⾯的递归只是为了演⽰递归的基本形式,不是为了解决问
题,代码最终也会陷⼊死递归,导致栈溢出(Stack overflow)。
1.递归的思想:
把⼀个⼤型复杂问题层层转化为⼀个与原问题相似,但规模较⼩的⼦问题来求解;直到⼦问题不能再被拆分,递归就结束了。所以递归的思考⽅式就是把⼤事化⼩的过程。
递归中的递就是递推的意思,归就是回归的意思。
2.递归的限制条件
递归在书写的时候,有2个必要条件:
递归存在限制条件,当满⾜这个限制条件的时候,递归便不再继续。
每次递归调⽤之后越来越接近这个限制条件。
二、递归举例
1.求n的阶乘
⼀个正整数的阶乘(factorial)是所有⼩于及等于该数的正整数的积,并且0的阶乘为1。
⾃然数n的阶乘写作 n!。
题⽬:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。
1.1代码的分析和实现
n的阶乘的公式: n! = n ∗ (n − 1)!
举例:
5!= 5 * 4 * 3 * 2 * 1
4!= 4 * 3 * 2 * 1
所以:5!= 5 * 4!
从这个公式不难看出:如何把⼀个较⼤的问题,转换为⼀个与原问题相似,但规模较⼩的问题来求解的。
n的阶乘和n-1的阶乘是相似的问题,但是规模要少了n。有⼀种有特殊情况是:当 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;
    else
        return n * Fact(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);
    int ret = Fact(n);
    printf("%d\n", ret);
     return 0;
}
结果:
(这里不考虑n太大的情况,n太大容易溢出)
2.画图演练
2.1举例
顺序打印⼀个整数的每⼀位
输⼊⼀个整数m,按照顺序打印整数的每⼀位。
⽐如:
输⼊:1234 输出:1 2 3 4
输⼊:125 输出:1 2 5
2.2代码的分析和实现
拿到这个题目,我们需要想的第一件事就是如何得到每一位数。
如果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打印的是⼀位数,直接打印就⾏。
2.3画图演练
以1234每一位的打印来推演一下

三、递归与迭代

递归是⼀种很好的编程技巧,但是和很多技巧⼀样,也是可能被误⽤的,就像举例1⼀样,看到推导的公式,很容易就被写成递归的形式:
int Fact(int n)
{
    if (n == 0)
        return 1;
    else
        return n * Fact(n - 1);
}
Fact函数是可以产⽣正确的结果,但是在递归函数调⽤的过程中涉及⼀些运⾏时的开销。
在C语⾔中每⼀次函数调⽤,都需要为本次函数调⽤在内存的栈区,申请⼀块内存空间来保存函数调⽤期间的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧。
函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次递归函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。
所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出的问题。
所以如果不想使⽤递归,就得想其他的办法,通常就是迭代的⽅式(通常就是循环的⽅式)。
⽐如:计算 n 的阶乘,也是可以产⽣1~n的数字累计乘在⼀起的。
1.举例
求第n个斐波那契数
以计算 factorial(5) 为例,执行过程如下:
factorial(5) 调用 5 * factorial(4)。
factorial(4) 调用 4 * factorial(3)。
factorial(3) 调用 3 * factorial(2)。
factorial(2) 调用 2 * factorial(1)。
factorial(1) 满足基本情况,返回 1。
然后逐步回溯计算:
factorial(2) 返回 2 * 1 = 2。
factorial(3) 返回 3 * 2 = 6。
factorial(4) 返回 4 * 6 = 24。
factorial(5) 返回 5 * 24 = 120。
由此可以看出,这里不适合使用递归,用迭代的方式效率更高。
递归的优缺点
优点:
代码简洁:递归可以让代码更加简洁、易读,尤其是处理一些具有递归结构的问题,如树的遍历、分治算法等。
易于理解:对于某些问题,递归的思路更贴合人类的思维方式,更容易设计和实现。
缺点:
性能问题:递归调用会频繁地进行函数调用和返回操作,带来额外的开销,可能会导致性能下降。
栈溢出风险:如果递归深度过大,会导致栈空间耗尽,引发栈溢出错误。
注意事项
要确保递归函数包含基本情况,防止无限递归。
注意递归深度,避免栈溢出。在必要时,可以考虑使用迭代(循环)来替代递归。
迭代是指基于一个初始值,按照特定的规则反复执行一系列操作,每一次执行都会对前一次的结果进行更新,直到满足某个终止条件为止。这个过程可以看作是对一个状态不断进行修改和演进的循环过程。
递归和迭代对比
执行效率:
迭代:通常情况下,迭代的执行效率相对较高,因为它避免了函数调用栈的频繁操作,只是在循环结构内进行简单的指令重复执行,减少了额外的开销。
递归:递归由于不断地进行函数调用和返回,会有一定的时间和空间开销,尤其是在递归深度较大时,可能会出现栈溢出等性能问题。
代码可读性:
迭代:对于一些简单逻辑,迭代代码的结构清晰,易于理解和调试,直接通过循环条件和循环体就能明白代码的执行流程。
递归:递归代码在处理具有递归结构的问题(如树的遍历、分治算法等)时,代码可能更简洁,更符合人们对问题的分解式思考方式,但对于初学者或者复杂逻辑,理解起来可能有一定难度。

迭代和递归各有优缺点,在实际编程中,需要根据问题的特点、性能要求和代码的可读性来选择合适的方法。有时候,迭代和递归可以相互转换,通过合理的设计和优化,可以提高代码的性能和可维护性。

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

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

相关文章

IdeaVim配置指南

一、什么是 IdeaVim&#xff1f; IdeaVim 是 JetBrains 系列 IDE&#xff08;如 IntelliJ IDEA, WebStorm, PyCharm 等&#xff09;中的一个插件&#xff0c;让你在 IDE 里使用 Vim 的按键习惯&#xff0c;大大提升效率。 安装方法&#xff1a; 在 IDE 中打开 设置(Settings) →…

Notepad++中XML格式化插件介绍

Notepad++中XML格式化插件介绍 背景安装指南安装步骤验证安装成功安装失败可尝试使用说明XML文件格式正确时格式化错误格式检查XML Tools插件核心功能盘点常见问题格式化后没变化中文显示乱码拯救杂乱XML格式!Notepad++这个神器插件,必须接收!背景 接手别人写的XML,缩进乱成…

自动化创业机器人:现状、挑战与Y Combinator的启示

自动化创业机器人&#xff1a;现状、挑战与Y Combinator的启示 前言 AI驱动的自动化创业机器人&#xff0c;正逐步从科幻走向现实。我们设想的未来是&#xff1a;商业分析、PRD、系统设计、代码实现、测试、运营&#xff0c;全部可以在monorepo中由AI和人类Co-founder协作完成…

第1章 算法设计基础

1-1 什么是算法 见识算法 算法是计算机科学的基石&#xff1a;从古代算术到现代计算机&#xff0c;算法始终是解决问题的核心。 算法的起源 张苍《九章算术》&#xff1a;创立了机械化算法体系&#xff08;如“合分术”分数相加算法&#xff09;。 欧几里得《几何原本》&am…

java中ArrayList扩容机制的解析

本文将系统地介绍 Java 中 ArrayList 的扩容机制&#xff0c;包括其初始容量的设置、触发扩容的时机、容量增长算法、扩容的详细流程以及性能优化建议&#xff0c;帮助读者从源码层面深入理解这一关键特性&#xff0c;并在实际开发中合理预分配容量以提升性能。 一、ArrayList…

【网络服务器】——回声服务器(echo)

作用 实现回声服务器的客户端/服务器程序&#xff0c;客户端通过网络连接到服务器&#xff0c;并发送任意一串英文信息&#xff0c;服务器端接收信息后&#xff0c;执行数据处理函数&#xff1a;将每个字符转换为大写并回送给客户端显示。 客户端&#xff1a;发送字符信息 服…

智能学习空间的范式革新:基于AI驱动的自习室系统架构与应用研究

摘要 在 “互联网 + 教育” 深度融合的背景下,传统自习室面临个性化服务缺失、学习效率低下等瓶颈。本文提出一种基于人工智能技术的 AI 自习室系统架构,通过构建多模态数据感知、个性化学习引擎及智能环境调控模块,实现学习过程的精准化、智能化与沉浸式体验。研究结合计算…

HTML01:HTML基本结构

HTML基本结构 <html> <head><meta charset"UTF-8"><title>我的第一个网页</title> </head> <body>我的第一个网页 </body> </html><body、</body等成对的标签&#xff0c;分别叫开发标签和闭合标签单独…

Spring Boot 实现多种来源的 Zip 多层目录打包下载(本地文件HTTP混合)

需要将一批文件&#xff08;可能分布在不同目录、不同来源&#xff09;打包成Zip格式&#xff0c;按目录结构导出给用户下载。 1. 核心思路 支持将本地服务器上的文件&#xff08;如/data/upload/xxx.jpg&#xff09;打包进Zip&#xff0c;保持原有目录结构。支持通过HTTP下载…

【Elasticsearch】在kibana中能获取已创建的api keys吗?

在 Kibana 中&#xff0c;目前没有直接的界面功能可以列出或查看已创建的 API 密钥&#xff08;API keys&#xff09;。API 密钥的管理和查看主要通过 Elasticsearch 的 REST API 来完成&#xff0c;而不是通过 Kibana 的管理界面。 在 Kibana 中使用 Dev Tools 查看 API 密钥…

公司项目架构搭建者

公司项目架构搭建者分析 项目架构搭建的核心角色 #mermaid-svg-FzOOhBwW3tctx2AR {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-FzOOhBwW3tctx2AR .error-icon{fill:#552222;}#mermaid-svg-FzOOhBwW3tctx2AR .err…

《技术驯化情感:AI伴侣、监控与伦理框架的重构挑战》

技术渗透与情感异化机制 情感计算技术正通过多种核心算法和数据处理方法深入人类生活&#xff0c;其在重构人类情感关系的同时也潜藏情感异化风险。本节从生物特征捕捉、行为模式诱导和认知框架重塑三方面解析情感计算的技术机理&#xff0c;并探讨其导致的情感依赖现象。 生物…

32单片机——独立看门狗

1、IWDG的简介 IWDG&#xff1a;Independent watchdog&#xff0c;即独立看门狗 独立看门狗本质上是一个定时器&#xff0c;该定时器是一个12位的递减计数器&#xff0c;当计数器的值减到0的时候&#xff0c;就会产生一个复位信号 如果在计数没减到0之前&#xff0c;重置计数器…

[计算机网络]数据链路层

408考纲(数链层部分): 0 概论&#xff1a;数据链路层都干什么事&#xff0c;提供啥功能 比物理层再高一层就是数据链路层&#xff0c;咱们上一篇讲物理层&#xff0c;物理层直接接触传输介质&#xff0c;现在数据链路层是使用物理层的传输服务&#xff0c;然后实现更多的功能。…

OpenAI大变革!继续与微软等,以非营利模式冲击AGI

今天凌晨2点&#xff0c;OpenAI宣布&#xff0c;将继续由非营利组织控制&#xff1b;现有的营利性实体将转变为一家公共利益公司&#xff1b;非营利组织将控制该公共利益公司&#xff0c;并成为其重要的持股方。 这也就是说OpenAI曾在去年提到的由非营利性转变成营利性公司&am…

库存怎么管?怎样才能做到有效的库存管理?

说到库存管理&#xff0c;估计大多数老板和管理者都有过“烦心事”。一方面&#xff0c;库存过多&#xff0c;货物堆积如山&#xff0c;堆在仓库里也不动&#xff0c;结果占地方还占用资金&#xff1b;另一方面&#xff0c;又有可能遇到客户急着要货&#xff0c;可是库存却紧张…

Kotlin-空值和空类型

变量除了能引用一个具体的值之外,还有一种特殊的值,那就是 null, 它代表空值, 也就是不引用任何对象 在Kotlin中, 对空值的处理是非常严格的,正常情况下,我们的变量是不能直接赋值为 null 的,否则无法编译通过, 这直接在编译阶段就避免了空指针问题 Kotlin中所有的类型默认都是…

[特殊字符]算法次元突破:螺旋矩阵的“能量解码术” vs 超立方体的“维度折叠指南”

&#x1f50d; 引言 如果科幻电影中的能量矩阵是算法的考题&#xff0c;你会用螺旋指针破解它的DNA吗&#xff1f; 如果《星际穿越》的五维空间变成编程题&#xff0c;你敢用动态规划丈量时间的褶皱吗&#xff1f; 今天&#xff0c;我们将化身算法世界的能量解…

高光谱相机赋能烟叶分选:精准、高效与智能化的新突破

烟草产业作为中国重要的经济支柱&#xff0c;烟叶分选的质量与效率直接影响行业效益。传统人工分选存在效率低、主观性强、标准难以统一等问题&#xff0c;而机器视觉技术受限于可见光波段&#xff0c;难以捕捉烟叶深层特征。深圳中达瑞和科技有限公司推出的高光谱相机解决方案…

矩阵求导常用公式解析:标量、向量与矩阵的导数计算

矩阵求导常用公式解析&#xff1a;标量、向量与矩阵的导数计算 矩阵求导常用公式解析&#xff1a;标量、向量与矩阵的导数计算矩阵求导的布局问题1. 分子布局 vs 分母布局对比表2. 布局冲突的典型场景分析3. 混合布局的兼容性处理 一、标量对向量求导1. 线性函数求导2. 二次型函…