C语言函数递归详解

递归是什么?

        递归,顾名思义,就是递推和回归。

        递归是一种解决问题的方法,在C语言中,递归就是函数自己调用自己。

#include <stdio.h>
int main()
{printf("hehe\n");main();//main函数中⼜调⽤了main函数return 0;
}

        上面就是C语言最简单的递归代码。但是这种代码最终会陷入死递归,导致栈溢出(Stack overflow)。

        递归的核心是思想和限制条件:

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为5之后,把5带入函数中,n为5,所以会返回5*Fact(4),而Fact(4)的值我们并不知道,同样需要带入计算,Fact(4)= 4 * Fact(3),依次进行下去,知到n = 0时返回1。我们的结果就是5*4*3*2*1*1 = 120.

        那我们就可以写一个函数Fact求n的阶乘,假设Fact(n)就是求n的阶乘,那么Fact(n-1)就是求n-1的阶乘,代码如下:
#include <stdio.h>
int Fact(int n)
{if(n==0)return 1;elsereturn n*Fact(n-1);
}
int main()
{int n = 0;scanf("%d", &n);int ret = Fact(n);printf("%d\n", ret);return 0;
}

这里的n不能太大,否则会出现溢出。

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

输⼊⼀个整数n,打印这个按照顺序打印整数的每⼀位。
例如:
输⼊:1234 输出:1 2 3 4
输⼊:520 输出:5 2 0
分析:这道题首先需要思考的时怎样得到整数n的每一位,如果n是一位数,那么n就是他自己,如果 n > 9 ,就要拆分n的每一位。
运算过程:
1234%10就能得到4,然后1234/10得到123,这就相当于去掉了4然后继续对123%10,就得到了3,再除10去掉3,以此类推不断的 %10 和 /10 操作,直到1234的每⼀位都得到。 这⾥有个问题就是得到的数 字顺序是倒着的,所以我们需要先回归最高位的数,那么就要先递推最低位的数,与我们上面思考的过程不谋而合。
我们假设想写⼀个函数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 函数是当到达限制条件后递推结束,才开始回归,所以最后推出的1是先打印的。

递归与迭代

        通过上面的举例,我们可以看出递归是一种很好的编程技巧,但是代码简洁的背后,是庞大的计算量。以代码举例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;elsereturn Fib(n-1)+Fib(n-2);
}
int main()
{int n = 0;scanf("%d", &n);int ret = Fib(n);printf("%d\n", ret); return 0;
}
        当我们n输⼊为50的时候,需要很⻓时间才能算出结果,这个计算所花费的时间,是我们很难接受的,这也说明递归的写法是⾮常低效.
        其实递归程序会不断的展开,在展开的过程中,我们很容易就能发现,在递归的过程中会有重复计算,⽽且递归层次越深,冗余计算就会越多.
        在计算第40个斐波那契数的时候,使⽤递归⽅式,第3个斐波那契数就被重复计算了39088169次,可见计算量很庞大。
        所以迭代的方式就显得高效的多:
#include <stdio.h>
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;
}
int main()
{int n = 0;scanf("%d", &n);
int ret = Fib(n);printf("%d\n", ret); printf("\ncount = %d\n", count);return 0;
}
有时候,递归虽好,但是也会引⼊⼀些问题,所以我们⼀定不要迷恋递归,适可而止最好。

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

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

相关文章

如何部署Linux AMH服务器管理面板并结合内网穿透远程访问

文章目录 1. Linux 安装AMH 面板2. 本地访问AMH 面板3. Linux安装Cpolar4. 配置AMH面板公网地址5. 远程访问AMH面板6. 固定AMH面板公网地址 AMH 是一款基于 Linux 系统的服务器管理面板&#xff0c;它提供了一系列的功能&#xff0c;包括网站管理、FTP 管理、数据库管理、DNS 管…

复旦大学NLP团队发布86页大模型Agent综述

复旦大学自然语言处理团队&#xff08;FudanNLP&#xff09;发布了一篇长达86页的综述论文&#xff0c;探讨了基于大型语言模型的智能代理的现状和未来。该论文从AI Agent的历史出发&#xff0c;全面梳理了基于大型语言模型的智能代理现状&#xff0c;包括LLM-based Agent的背景…

优秀学习网站推荐-第一辑

原文地址&#xff1a;https://jaune162.blog/2024/02/15/study-website-recommend Developer Roadmaps&#xff08;开发者路线图&#xff09; 官网地址&#xff1a;https://roadmap.sh/ 该网站包含了各个方向、各个语言的开发人员从零开始学习的路线图。 下图为Java方向的学…

Jenkins配置http请求github,发布release

学无止境&#xff0c;气有浩然&#xff01; Jenkins配置http请求github&#xff0c;发布release 前言Jenkins配置github配置在这里插入图片描述 打完收工! 前言 工作中进行了github迁移&#xff0c;原先的gitlab中配置的Jenkins的CI/CD步骤需要发布到Github发布release版本&am…

2024年人工智能可以报考的证书有哪些

人工智能&#xff08;AI&#xff09;是致力于解决通常与人类智能相关联的认知性问题的计算机科学领域&#xff0c;这些问题包括学习、创造和图像识别等。现代组织从各种来源收集大量数据&#xff0c;例如智能传感器、人工生成的内容、监控工具和系统日志。人工智能的目标是创建…

LeetCode:292.Nim 游戏

大一开学到现在&#xff0c;我不禁思考一个问题&#xff1a;代码重要吗&#xff1f; 我的答案是&#xff0c;根本不重要&#xff0c;或者说&#xff0c;是次要的。我认为分析问题&#xff0c;和画图是写题的开始&#xff0c;方法的学习&#xff0c;和灵活运用是目的。代码从来…

贪心算法篇2

“星辰野草&#xff0c;造出无边的天地~” 最⻓递增⼦序列 (1) 题目解析 (2) 算法原理 class Solution { public:int lengthOfLIS(vector<int>& nums) {// 使用dp int n nums.size(), ret 1;// 初始化为1vector<int> dp(n1,1);// 从第二个位置…

Spring速成(二)

Spring速成&#xff08;二&#xff09; 掌握IOC/DI配置管理第三方bean掌握IOC/DI的注解开发掌握IOC/DI注解管理第三方bean完成Spring与Mybatis及Junit的整合开发 1&#xff0c;IOC/DI配置管理第三方bean 1.1 案例:数据源对象管理 1.1.1 环境准备 学习之前&#xff0c;先来准…

LFU缓存(Leetcode460)

例题&#xff1a; 分析&#xff1a; 这道题可以用两个哈希表来实现&#xff0c;一个hash表&#xff08;kvMap&#xff09;用来存储节点&#xff0c;另一个hash表&#xff08;freqMap&#xff09;用来存储双向链表&#xff0c;链表的头节点代表最近使用的元素&#xff0c;离头节…

Deepin系统安装x11vnc远程桌面工具实现无公网ip访问本地桌面

文章目录 1. 安装x11vnc2. 本地远程连接测试3. Deepin安装Cpolar4. 配置公网远程地址5. 公网远程连接Deepin桌面6. 固定连接公网地址7. 固定公网地址连接测试 x11vnc是一种在Linux系统中实现远程桌面控制的工具&#xff0c;它的原理是通过X Window系统的协议来实现远程桌面的展…

Spring Boot整合MyBatis Plus实现基本CRUD与高级功能

文章目录 1. 引言2. 项目搭建与依赖配置2.1 添加MyBatis Plus依赖2.2 配置数据源与MyBatis Plus 3. 实现基本CRUD功能3.1 创建实体类3.2 创建Mapper接口3.3 实现Service层3.4 控制器实现 4. 高级功能实现4.1 自动填充功能4.2 乐观锁功能4.3 逻辑删除功能 5. 拓展&#xff1a;My…

构造回文数组

目录 原题描述&#xff1a; 题目描述 时间&#xff1a;1s 空间&#xff1a;256M 题目描述&#xff1a; 输入格式&#xff1a; 输出格式&#xff1a; 样例1输入&#xff1a; 样例1输出&#xff1a; 样例2输入&#xff1a; 样例2输出&#xff1a; 约定&#xff1a; 作…

ubantu扩容解决 sudo -i 报无法识别

GParted给ubuntu系统磁盘resize大小时候出现cannot resize read-only file system解决办法_gparted无法调整分区大小-CSDN博客https://blog.csdn.net/ningmengzhihe/article/details/127295333?spm1001.2014.3001.5506 解决磁盘挂载系统目录出现问题&#xff1a; 1、sudo -i…

【HarmonyOS应用开发】后台提醒(十六)

简述 随着生活节奏的加快&#xff0c;我们有时会忘记一些重要的事情或日子&#xff0c;所以提醒功能必不可少。应用可能需要在指定的时刻&#xff0c;向用户发送一些业务提醒通知。例如购物类应用&#xff0c;希望在指定时间点提醒用户有优惠活动。为满足此类业务诉求&#xf…

C语言-分支和循环语句

目录 分支语句 if语句 关系操作符&#xff1a; 逻辑操作符&#xff1a; switch语句 循环语句 while循环 for循环 ​编辑 循环控制语句&#xff08;break&#xff0c;continue&#xff09; goto语句充当循环 分支语句 if语句 if语句的括号内需要写条件表达式&#xff0c;通…

超多制作模板的姓氏头像生成器微信小程序源码

超多制作模板的姓氏头像生成器微信小程序源码&#xff0c;这是一款姓氏头像制作小工具&#xff0c;内含丰富多样的模板提供制作。 以前的基本是固定位置生成&#xff0c;这款制作支持拖拽调整位置&#xff0c;自定义颜色&#xff0c;阴影等等。

探索设计模式的魅力:外观模式简化术-隐藏复杂性,提供简洁接口的设计秘密

设计模式专栏&#xff1a;http://t.csdnimg.cn/U54zu 目录 引言&#xff1a;探索简化之路 一、起源和演变 二、场景案例分析 2.1 不用模式实现&#xff1a;用一坨坨代码实现 2.2 问题 2.3 外观模式重构代码 定义 界面 接口 利用外观模式解决问题步骤 外观模式结构和说明 重构…

Linux 网络:PTP 简介

文章目录 1. 前言2. PTP(Precision Time Protocol​) IEEE 1588 协议简介2.1 PTP IEEE 1588 协议时间同步原理2.2 PTP IEEE 1588 协议时钟类型2.2.1 普通时钟(OC: Ordinary Clock)2.2.2 边界时钟(BC: Boundary Clock)2.2.3 透明时钟(TC: Transparent Clock)2.2.3.1 端对端透明时…

【论文研读】Better Together:Unifying Datalog and Equality Saturation

最近研究ReassociatePass整的头大&#xff0c;翻两篇Datalog的论文看看。 今天看的一篇是比较新的文章&#xff0c;23年4月贴到arxiv上的。 本文的主要贡献是提出了egglog,将Datalog和Eqsat结合起来&#xff0c;继承了Datalog的efficient incremental execution, cooperating a…

【前端web入门第四天】02 CSS三大特性+背景图

文章目录: 1. CSS三大特性 1.1继承性 1.2 层叠性 1.3 优先级 1.3.1 优先级1.3.2 优先级-叠加计算规则 2. 背景图 2.1 背景属性2.2 背景图2.3 背景图的平铺方式2.4 背景图位置2.5 背景图缩放2.6 背景图固定2.7 背景复合属性 1. CSS三大特性 1.1继承性 什么是继承性? 子级默…