当前位置: 首页 > news >正文

C语言中的套娃——函数递归

目录

一、什么是递归

1.1.递归的思想

1.2.递归的限制条件

二、举例体会

2.1.求n的阶乘 

2.2.顺序打印整数的每一位

2.3.斐波那契数列

三、递归与迭代


一、什么是递归

在学习C语言的过程中,我们经常会跟递归打交道,什么是递归呢?它其实是一种解决问题的方法,递归递归,顾名思义,递推回归。在C语言中,函数自己调用自己就是递归,我们可以把它想成生活中的俄罗斯套娃。

下面请看最简单的递归代码:

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

在上面的代码中,我们看到了main函数里再次调用了main函数,我们可以想象,这个程序会一直调用下去,直到,内存不够导致栈溢出(Stack overflow)。

1.1.递归的思想

递归的思想用一个词来讲就是“大事化小”。

其中代表递推代表回归。

1.2.递归的限制条件

刚刚我们看到,一直调用main函数的话,会造成死递归,因此,我们在使用递归时需要注意一些必要条件。

1.递归存在限制条件,当超过这个限制条件时递归就应该停止

2.每次递归应该越来越接近这个限制条件。 

接下来我们举几个例子来让大家体会一下这两个必要条件。

二、举例体会

2.1.求n的阶乘 

⼀个正整数的阶乘(factorial)是所有⼩于及等于该数的正整数的积,并且0的阶乘为1。
 
自然数n的阶乘写作 n! 。

经分析可知n! = n * (n-1) * (n-2)... * 3 * 2 * 1,而(n-1)! = (n-1) * (n-2) *...* 3 * 2 * 1。

所以n! = n * (n-1)!。

我们要求n的阶乘,只需要求n和n-1的阶乘的乘积,问题也就变成了求n-1的阶乘。经过一次递归,我们就从n变到n-1,那递归的次数足够了,我们就可以到最后的1的阶乘。那怎么得到n的阶乘呢,我们刚刚一步一步得到1的阶乘,那我们再一步一步乘回去,最终得到n的阶乘。

上述思路就是所谓的递归,也就是把一个较大的问题转换为与原问题相似的小问题。

当n = 0时,n! = 1。我们可以得到递推公式:

代码如下:

函数部分

int Fact(int n)
{if(n==0)return 1;elsereturn n*Fact(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;
}

测试结果

2.2.顺序打印整数的每一位

输入一个整数n,顺序打印其每一位。

input : 1234

output : 1 2 3 4

分析可知,1234/10 = 123,而1234%10 = 4。那我们可以巧妙的利用上述特性,得到1234的每一位。但是出现一个问题,我们获得的数字的顺序是倒着的,这该怎么办呢。我们可以仔细品味一下递归,递推和回归,先递推再回归。

我们就可以先进行/10的操作,再打印%10的余数,如下:

void Print(int n)
{if(n>9){Print(n/10);}printf("%d ", n%10);
}

画图推演一下:

代码如下:

#include<stdio.h>
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;
}

运行结果:

2.3.斐波那契数列

斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称“兔子数列”,其数值为:1、1、2、3、5、8、13、21、34…… 

其递推公式为

用递推写出代码很简单:

#include<stdio.h>int Fib(int n) 
{if (n == 1 || n == 2)return 1;else return Fib(n - 1) + Fib(n - 2);
}int main()
{int n = 0;scanf("%d", &n);printf("%d", Fib(n));return 0;
}

运行结果:

那如果让你不用递归的方法,你会怎么做呢? 

我们可以创建三个变量,就像两个数互相交换那样,将a赋值1,b赋值1,c为a与b的和。

n大于二之后才开始循环,所以我们可以这么写:

int Fib(int n) 
{int a = 1, b = 1,c = 0;while (n>2){c = a + b;a = b;b = c;n--;}return b;
}

 一个接着一个交换值,直到n等于2,退出循环,此时c的值赋给了b,而我们在n小于等于2的时候,求不出来c,而b的值正好是1,所以我们返回b的值。

三、递归与迭代

上面我们说了什么是递归,这又来个迭代,什么叫迭代呢?说白了通常就是循环。

比如刚才计算阶乘,我就不想用递归,那我就循环n次,也可以解决问题,并且该方法效率比递归高。

我们遇到的许多问题用递归解释的原因是因为,它比非递归好想好解释,但这些问题往往迭代比递归的效率更高。

我们说当一个问题非常复杂,难以用迭代的方式来解决时2,这时候递归实现的简洁性便可以补偿运行时的开销。

就像刚刚的例三,求斐波那契数列,使用迭代的方法就更加有效率。

如图所示,递归层次越深,冗余计算越多,我们可以简单测试一下

#include <stdio.h>
int count = 0;
int Fib(int n)
{if(n == 3)count++;//统计第3个斐波那契数被计算的次数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); printf("\ncount = %d\n", count);return 0;
}

 来看结果

这才是40,可想而知50会是多大的天文数字。

而迭代的方式,我们只需要前后一步一步相加即可。


最后总结一下,递归是一个很好的解决问题方式,在编程学习中,我们会经常用到它,但是它也不是万能的,还是需要我们多动脑思考。

我相信,我们总会找到解决办法的。

相关文章:

  • 【力扣白嫖日记】178.分数排名
  • 基于JavaWeb实现的校园新闻发布系统
  • 国产替代MATLAB的征途
  • 推荐收藏!科大讯飞算法岗(NLP 方向)面试题7道(含答案)
  • pytest基本应用
  • 网络安全与信创产业发展:构建数字时代的护城河
  • BFS中的多源BFS-双端队列BFS
  • 掌握 Android 中的 RecyclerView 优化
  • 中级.NET开发工程师面试经历
  • petalinux_zynq7 驱动DAC以及ADC模块之一:建立IP
  • 【论文精读】OS-Copilot: Towards Generalist Computer Agents with Self-Improvement
  • 考研408深度分析+全年规划
  • google浏览器chrome无法访问localhost等本地虚拟域名的解决方法
  • 第三章 Web 网关支持的配置
  • 微信小程序本地开发
  • [译]CSS 居中(Center)方法大合集
  • 【RocksDB】TransactionDB源码分析
  • Angular 响应式表单 基础例子
  • css布局,左右固定中间自适应实现
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • es的写入过程
  • Java IO学习笔记一
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • JS基础之数据类型、对象、原型、原型链、继承
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • Netty源码解析1-Buffer
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • python学习笔记-类对象的信息
  • vue-loader 源码解析系列之 selector
  • 不上全站https的网站你们就等着被恶心死吧
  • 从重复到重用
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 入门级的git使用指北
  • 深入浏览器事件循环的本质
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • #我与Java虚拟机的故事#连载06:收获颇多的经典之作
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (转载)虚函数剖析
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .MSSQLSERVER 导入导出 命令集--堪称经典,值得借鉴!
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .net 程序 换成 java,NET程序员如何转行为J2EE之java基础上(9)
  • .NET 依赖注入和配置系统
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • .net对接阿里云CSB服务
  • @RequestMapping-占位符映射
  • [20171102]视图v$session中process字段含义
  • [AIGC] SQL中的数据添加和操作:数据类型介绍
  • [autojs]autojs开关按钮的简单使用
  • [BUUCTF 2018]Online Tool(特详解)
  • [BUUCTF]-PWN:[极客大挑战 2019]Not Bad解析