C语言——函数递归

大家好,今天和大家浅浅的聊一下函数的递归,能力有限,感谢支持!

ea499de7364a4cf291bae2f67fd7210c.jpg

一、什么是递归

说到递归,或许有的小伙伴听过,但对于我们来说是一个比较陌生的词。那什么是递归呢?

递归其实是⼀种解决问题的⽅法,在C语⾔中,递归就是函数⾃⼰调⽤⾃⼰。
接下来,我们先看一段简单的代码:388fcbb4465241cf9d1d3e192fd567ef.png

在上述代码中我们看到,main函数调用用了自己。这就是⼀个简单的递归程序,只不过上⾯的递归只是为了演⽰递归的基本形式,不是为了解决问题,代码最终也会陷⼊死递归,导致栈溢出(Stack overflow)。0bab40a7ee594c65bb75f87536efeb00.png

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

在编程的过程中,这种递归的思想是非常重要的,它往往能简化一个大问题,提高编程和程序的效率。

二、递归的限制条件

递归虽好,但也不是什么问题都能递归,它也有条件限制:

递归在书写的时候,有2个必要条件:

(1)递归存在限制条件,当满⾜这个限制条件的时候,递归便不再继续。

(2)每次递归调⽤之后越来越接近这个限制条件。

如果没有限制条件,那么就会一直递归下去,最后可能导致栈溢出或其他问题,接下来,我们就通过具体的例子来感受之两个条件。

三、递归举例

1.求n的阶乘

用普通方法求n的阶乘很简单,只需要一个循环即可。那么递归怎么解决呢?

我们先来分析:

我们知道n的阶乘的公式:n!=n*(n-1)*(n-2)....* 1。如:5!=5*4*3*2*1。

再观察我们知道n的阶乘还可以表示:n ! = n * (n-1) !

ca84897c2f3745e0ae56bcf4f0bea956.png

这样的思路就是把⼀个较⼤的问题,转换为⼀个与原问题相似,但规模较⼩的问题,来求解的。
再稍微分析⼀下,当 n<=1 的时候,n的阶乘是1,其余n的阶乘都是可以通过上述公式计算。n的阶乘的递归公式如下:

338108ca6fbb40c2808ef3f09e9c8d29.png

那我们就可以写出函数Fact求n的阶乘,假设Fact(n)就是求n的阶乘,那么Fact(n-1)就是求n-1的阶乘,函数如下:

f38fede15fa04b91953c15112cfa0606.png

我们来测试一下(这⾥不考虑n太⼤的情况,n太⼤存在溢出):
1afefde06e744278866aa66667046229.png

下面画图推演一下:eafe9cb7b3014fed885bd3119b42ec96.png

我们上面说了递归在书写的时候,有2个必要条件,而在这里n<=1就是上述递归的第一个条件,而每递归一次n就减1越来越接近1,就是第二个条件。

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

输⼊⼀个整数m,打印这个按照顺序打印整数的每⼀位

如:

0b6ee3d8510348469a2ed4a2150a8aaa.png

和上面一样我们先来推理分析:

我们首先要思考的就是如何得到每一位的数字,假设n是⼀位数,n的每⼀位就是n⾃⼰,n是超过1位数的话,就得拆分每⼀位。

1234%10就能得到4,然后1234/10得到123,这就相当于去掉了4,然后继续对123%10,就得到了3,再除10去掉3,以此类推不断的 %10 和 \10 操作,直到1234的每⼀位都得到,但是这⾥有个问题就是得到的数字顺序是倒着的但是我们有了灵感,我们发现其实⼀个数字的最低位是最容易得到的,通过%10就能得到那我们假设想写⼀个函数Print来打印n的每⼀位,如下表⽰:c8f45e81626e4182a0077615152d8fe5.png

所以就得到:ad05655cbcc8400a984e7eeee7df7fa3.png

函数如下:3bcdad4f849643bfbfabf05fb698c9e3.png

来测试一下:f3deeb8e06e34bff8633f1fc597d4c52.png

下面画图推演一下:ab80c117d5b346af8d306c17482d5061.png

这个递归它的第一个条件是:它的限制条件是n>9也就是n要是两位数以上,当n是个位数时开始回溯;第二个条件是不断地 /10减少位数,越来越接近限制条件。

四、递归与迭代

递归是⼀种很好的编程技巧,但是很多技巧⼀样,也是可能被误⽤的,就像举例1⼀样,看到推导的公式,很容易就被写成递归的形式:
338108ca6fbb40c2808ef3f09e9c8d29.png

Fact函数是可以产⽣正确的结果,但是在递归函数调⽤的过程中涉及⼀些运⾏时的开销。
在C语⾔中每⼀次函数调⽤,都要需要为本次函数调⽤在栈区申请⼀块内存空间来保存函数调⽤期间的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧。函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次递归函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(stack overflow)的问题。
所以如果不想使⽤递归就得想其他的办法,通常就是迭代的⽅式(通常就是循环的⽅式)。

⽐如:计算n的阶乘,也是可以产⽣1~n的数字累计乘在⼀起。

7382a50c8fe54f81899eb3460c885d2e.png

上述代码是能够完成任务,并且效率是⽐递归的⽅式更好的。
事实上,我们看到的许多问题是以递归的形式进⾏解释的,这只是因为它⽐⾮递归的形式更加清晰,但是这些问题的迭代实现往往⽐递归实现效率更⾼。当⼀个问题⾮常复杂,难以使⽤迭代的⽅式实现时,此时递归实现的简洁性便可以补偿它所带来的运⾏时开销。
下面我们就来看一个例子:

求第n个斐波那契数
斐波那契数列我们早就听说过,也就是兔子数列。它用递归来求解就是很不适合的。c09ec8459f7d41eaaaa2e85de031bb11.png

我们可以看到它的通项公式是:F(n)=F(n-1)+F(n-2)  (n>=2).

所以我们可以推出递归的形式是:ac61e5b45d364aecb23d4c8c7d3e96ba.png

写成函数就是:2c6e056e1a1845d7aee5c61e0c71053f.png

测试一下:b92cea0ede734d318dfee3bda4055723.png

我们看到好像没有什么问题,但是n的值一旦很大,需要很⻓时间才能算出结果,这个计算所花费的时间,是我们很难接受的,这也说明递归的写法是⾮常低效的。

de4ea5f7d89f41e190625a16ac539ab1.png

那这是什么原因呢?我们推演一下就知道了。

5ed2e4d2b6aa44738417080ee550522b.png

其实递归程序会不断的展开,在展开的过程中,我们很容易就能发现,在递归的过程中会有重复计算,⽽且递归层次越深,冗余计算就会越多,我们可以测试一下:d96c1845ceea4ee39620f3ccf0b2d9eb.png

我们可以看到,在计算第30个斐波那契数的时候,使⽤递归⽅式,第3个斐波那契数就被重复计算了317811次,这些计算是⾮常冗余的。所以斐波那契数的计算,使⽤递归是⾮常不明智的,我们就得想迭代的⽅式解决。

如:
61219d3113cf455480fdee7f2abc5514.png

迭代的⽅式去实现这个代码,效率就要⾼出很多了。

所以, 有时候,递归虽好,但是也会引⼊⼀些问题,所以我们⼀定不要迷恋递归,适可⽽⽌就好。

今天只是浅浅的了解了一下递归,递归在后面还有很多用处,等以后我们学到数据结构就会了解更多递归的知识。
好了,以上就是本文的全部内容了,感谢大家的观看,如有错误欢迎指正!创作不易,三连支持一下呗🥰

4cdc1af400164834a4366bbbb4eec4f0.jpg

  • 32
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值