【C语言】函数递归(详解)
文章目录
- 函数递归
- 什么是递归?
- 递归的俩个必要条件
- 代码引例1
- 栈溢出(Stack Overflow)
- 合理使用递归
- 代码引例3
- 代码引例4
- 解释要合理使用递归
- 结束语
函数递归
程序调用自身的编程技巧称为递归 recursion)
函数自己调用自己就是递归
你也可以理解成是一种嵌套结构,但递归分为俩部分,第一是“递”,进入嵌套结构。第二是”归“,最终会一步一步返回。第一次接触递归都会很懵,慢慢理解这个过程就明白了。
什么是递归?
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接
调用自身的
一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,
递归策略
只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
递归的俩个必要条件
代码引例1
接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:123,输出 1 2 3
参考代码:
#include <stdio.h>
void print(int n) {if(n>9){print(n/10);}printf("%d ", n%10);
}
int main()
{int num = 123;print(num);return 0; }
运行结果如下:
我们要怎么理解这个函数递归的实现呢
我们可以采用画图方式理解这个过程
所以我们可以看到,递归必须满足俩个必要条件:
1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。
2.每次递归调用之后越来越接近这个限制条件。
题中的限制条件就是(n>9),当我们的n通过(n/10)越来越少,直至n=1,无法满足时,递归停止,并开始返回。
这里有一个重要点就是print(n/10),如果没有这个条件,换成print(n)的话,n一直无法减小,一直进行递归。最后会导致栈溢出
(Stack Overflow)。
栈溢出(Stack Overflow)
关于栈溢出,我就先简单介绍一下栈
栈:栈是一种计算机系统中的数据结构,它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来),是一种特殊的线性表。栈的操作常用的有进栈(PUSH),出栈(POP),还有常用的标识栈顶和栈底。
可以把栈想象成一摞扑克牌一样,一张一张叠加起来。
而栈溢出呢是缓冲区溢出的一种,缓冲区溢出:简单的说,缓冲区溢出就是超长的数据向小缓冲区复制,导致数据超出了小缓冲区,导致缓冲区其他的数据遭到破坏,这就是缓冲区溢出。而栈溢出是缓冲区溢出的一种,也是最常见的。只不过栈溢出发生在栈,堆溢出发生在堆,其实都是一样的。
而在代码引例1中
系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一
直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出
合理使用递归
使用递归的宗旨是把大事化小
所以遇到问题时,我们应该明白是要把问题简单化,而不是习惯用递归,就一直用递归思考问题
我们应该清楚是不是用递归的思想会比较简单,或者换成递归的思想也可以实现,我们可以通过例题明白
代码引例3
求n的阶乘
用循环的方法,代码如下:
int main()
{int n = 0;int ret = 1;scanf("%d", &n);//循环产生1~n的数字int i = 0;for (i = 1; i <= n; i++){ret = ret * i;}printf("ret = %d\n", ret);return 0;
}
而采用递归的话,代码如下:
int fac(int n)
{if (n <= 1)return 1;elsereturn n * fac(n - 1);
}int main()
{int n = 0;scanf("%d", &n);int ret = fac(n);printf("%d\n", ret);return 0;
}
很多人刚学递归,都是懂了,但不知道怎么用。
而这道题可以先用公式来理解题目,再来用递归就容易多了
再来对比一下函数的代码,是不是清晰明了呢
代码引例4
求第n个斐波那契数。(不考虑溢出)
代码如下:
int fib(int n) {if (n <= 2) return 1;elsereturn fib(n - 1) + fib(n - 2);
}
这道题同样我们也可以利用公式构造一个函数实现递归
具体思路如下:
解释要合理使用递归
通过以上俩个例题,我们可以发现俩个问题:
在使用 fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。
使用 factorial 函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。
为什么呢?
我们发现 fib 函数在调用的过程中很多计算其实在一直重复。
如果我们把代码修改一下:
int count = 0;//全局变量
int fib(int n) {if(n == 3)count++;if (n <= 2) return 1;elsereturn fib(n - 1) + fib(n - 2);
最后我们输出看看count,是一个很大很大的值。
那我们如何改进呢?
在调试 factorial 函数的时候,如果你的参数比较大,那就会报错: stack overflow(栈溢出)
这样的信息。
那如何解决上述的问题:
将递归改写成非递归。
使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代
nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问
比如,下面代码就采用了,非递归的方式来实现:
//求n的阶乘
int factorial(int n) {int result = 1;while (n > 1){result *= n ;n -= 1;}return result; }
//求第n个斐波那契数
int fib(int n) {int result;int pre_result;int next_older_result;result = pre_result = 1;while (n > 2){n -= 1;next_older_result = pre_result;pre_result = result;result = pre_result + next_older_result;}return result; }
提示:
许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销
结束语
本人是学c小白,这些是近期学习整理总结,有什么不对欢迎大家指正,我会继续努力,谢谢~!
【C语言】函数递归(详解)相关推荐
- 【C语言】函数递归详解
函数递归 1. 什么是函数递归 2. 递归的两个必要条件 2.1 练习1:打印一个数的每一位 2.2 练习2:求字符串长度(strlen 模拟实现) 3. 递归与迭代 3.1练习3:求 n 的阶乘(不 ...
- C++语言函数重载详解和示例
C++ 函数重载的概念 在实际开发中,有时候我们需要实现几个功能类似的函数,只是有些细节不同.例如把变量的值写入文件,变量的类型int.long.double.char,需要通过参数把变量传入函数.在 ...
- C语言---函数介绍详解
生活的全部意义在于无穷地探索尚未知道的东西,在于不断地增加更多的知识--左拉 文章目录 前言 函数 模块化程序设计方法 函数的定义 函数的分类 函数定义角度 库函数 自定义函数 函数形式角度 无参函数 ...
- 七、C语言函数定义详解
文章目录 1.函数的声明和定义 2.函数的调用 3.函数参数的两种传递方式 1.传值调用(C语言默认传递参数方式) 2.引用调用 4.函数的分类 1.内部函数(静态函数) 2.外部函数 1.函数的声明 ...
- 计算机科学类专升本复习之“C语言函数定义”详解(初稿)
今天首先要讲的是C语言的无参函数定义 函数 如果不接收"用户传递"的数据,那么定义时可以不带参数 例如: dataType kangkang(){ //nb } dataType: ...
- 【C语言函数参数详解】——实际参数(实参)形式参数(形参)
文章目录 一.什么是实际参数(实参) 二.什么是形式参数(形参) 三.形参与实参的关系 这篇文章我们一起学习一下函数的参数,函数的参数分为实参和形参. 一.什么是实际参数(实参) 首先我们来学习实参, ...
- 什么叫c语言函数递归,什么是递归-C语言函数递归-嗨客网
C语言函数递归教程 函数递归就是一个 C语言函数递归条件 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈). 函数的局部 递归必须向退出递归的条件逼近,否则就是无限递归了. 当一个函数执行完 ...
- R语言tidyr包gather()函数实战详解:数据收缩、从宽表到窄表
R语言tidyr包gather()函数实战详解:数据收缩.从宽表到窄表 目录 R语言tidyr包gather()函数实战详解:数据收缩.从宽表到窄表 收缩两列数据
- R语言tidyr包spread()函数实战详解:数据裂变、从窄表到宽表
R语言tidyr包spread()函数实战详解:数据裂变.从窄表到宽表 目录 R语言tidyr包spread()函数实战详解:数据裂变.从窄表到宽表
- R语言tidyr包Unite()函数实战详解:多个数据列合并为一列
R语言tidyr包Unite()函数实战详解:多个数据列合并为一列 目录 R语言tidyr包Unite()函数实战详解:多个数据列合并为一列
最新文章
- 使用Nginx为Leanote配置Https
- linux 代码获取当前路径,【linux】shell代码,获取当前路径,创建文件夹
- XMLHttpRequest Object
- Hangry 饥饿成怒 2016-10-03
- 时间同步-ntp服务器的搭建(docker版本)
- MS UC 2013-0-虚拟机-标准化-部署-1-虚拟化-部署
- k折交叉验证法python实现_Jason Brownlee专栏| 如何解决不平衡分类的k折交叉验证-不平衡分类系列教程(十)...
- ajax中html的属性,jQuery Ajax加载html数据正常,但属性似乎'不可读'
- opencv3和qt5计算机视觉应用开发pdf_NeurIPS 2019:计算机视觉论文回顾
- IPC生产者与消费者模型加线程
- python 协程爬虫_Python爬虫进阶教程(二):线程、协程
- 史上最全CSDN中免积分下载攻略
- oppok3如何刷机_oppok3刷机方法
- HTML设为首页/加入收藏代码
- MapGIS K9三维TDE平台实现动态光标效果
- Premiere 视频基本调色
- python模拟ios点击_使用Xcode + Python进行IOS运动轨迹模拟
- shopNC注册后无法登陆的问题
- cc2530:<3>ADC采集光照度案例
- codeforces-1734C - Removing Smallest Multiples