前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C语言】函数递归(含扫雷进阶思路)

【C语言】函数递归(含扫雷进阶思路)

作者头像
TANGLONG
发布2024-10-15 19:06:43
1040
发布2024-10-15 19:06:43
举报
文章被收录于专栏:C/C++/数据结构/算法

一、什么是递归

    递归是学习C语⾔函数绕不开的⼀个话题,那什么是递归呢?     递归其实是⼀种解决问题的⽅法,在C语⾔中,递归就是函数⾃⼰调⽤⾃⼰。 写⼀个史上最简单的C语⾔递归代码:

    上述就是⼀个简单的递归程序,只不过上⾯的递归只是为了演⽰递归的基本形式,不是为了解决问题,代码最终也会陷⼊死递归,导致栈溢出,因为代码每执行完printf时,又调用了main函数,也就是又从main函数的头开始,然后再打印,最后一陷入死递归,如果代码突然结束,可能就是程序一直在创建函数栈帧,导致了栈溢出

二、递归的使用思路和限制条件

1.递归的使用思路

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

2.递归的限制条件

递归在书写的时候,有2个必要条件:     • 递归存在限制条件,当满⾜这个限制条件的时候,递归便不再继续,如果没有限制,可能会陷入死递归     • 每次递归调⽤之后越来越接近这个限制条件。 在下⾯的例⼦中,我们逐步体会这2个限制条件

三、递归的举例

举例1:求n的阶乘

    ⼀个正整数的阶乘(factorial)是所有⼩于及等于该数的正整数的积,并且0的阶乘为1。 ⾃然数n的阶乘写作n! (1)我们知道n的阶乘的公式: n! = n ∗ (n − 1)! 如:

代码语言:javascript
复制
      5! = 5*4*3*2*1
      4! = 4*3*2*1
   //所以:5! = 5*4!

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

    所以我们可以在函数fact中调用fact函数,实现递推,每次递推n都减1,直到n等于0,随后函数开始返回,最后算出n的阶乘,如:

运行结果:

(3)画图整个过程演示:

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

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

代码语言:javascript
复制
输⼊:1234 输出:1 2 3 4
输⼊:520  输出:5 2 0

(1)分析:     这个题⽬,放在我们⾯前,⾸先想到的是,怎么得到这个数的每⼀位呢?     如果n是⼀位数,n的每⼀位就是n⾃⼰,n是超过1位数的话,就得拆分每⼀位     拆分的方法之前也讲到过,就是%10可以得到最后一位,/10可以去掉最后一位,但是我们要按照顺序打印,一个思路就是直接按上面的方法处理,然后再将它倒着打印即可,我们接下来将的是使用递归的思路     想要用递归解决这个问题,那么我们就要明白使用递归的方法思路,也就是将一个大的问题逐步的化解为一个又一个的小问题,先递推,然后到了某种条件再回归,最后帮我们实现任务     比如我们现在有一个函数叫print,它的作用就是帮我们将一个整数的每一位给打印出来,假如打印1234的每一位,那么就可以拆分成print(123) + print(4),然后print(123)又可以拆分为print(12) + print(3),以此类推,如下:

代码语言:javascript
复制
 Print(1234)
==>Print(123) + printf(4)
==>Print(12) + printf(3)
==>Print(1) + printf(2)
==>printf(1) 

    那么递归如何结束呢?我们可以思考,什么情况下函数无需再次递归,没错,就是当这个数只剩下一位数的时候,我们就可以直接将它打印出来,无需递归,那么怎么判断这个数是不是一位数呢?我们就可以将9这个界限找出来,如果一个整数大于9那么它肯定不是一位数,反之它就是个一位数,现在限制条件也清楚了,这个代码也就迎刃而解了

(2)代码实现以及运行结果:

    在这个解题的过程中,我们就是使⽤了⼤事化⼩的思路,把print(1234) 打印1234每⼀位,拆解为⾸先print(123)打印123的每⼀位,再打印得到的4     把print(123) 打印123每⼀位,拆解为⾸先print(12)打印12的每⼀位,再打印得到的3 直到Print打印的是⼀位数,直接打印就⾏

(3)画图演示:

四、递归与迭代对比

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

    在C语⾔中每⼀次函数调⽤,都需要为本次函数调⽤在内存的栈区,申请⼀块内存空间来保存函数调⽤期间的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧     函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次递归函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间     所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(stack overflow)的问题,关于函数栈帧的创建与销毁的详细过程,会在以后的视频进行讲解     如果不想使⽤递归,就得想其他的办法,通常就是迭代的⽅式(通常就是循环的⽅式) ⽐如:计算 n 的阶乘,也是可以产⽣1~n的数字累计乘在⼀起的,如图:

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

五、递归与迭代对比举例

需求:求第n个斐波那契数     计算第n个斐波那契数,是不适合使⽤递归求解的,但是斐波那契数的问题通过是使⽤递归的形式描述的,如下:

    看这个形式,很容易又到我们写出递归,如:

    当我们输入50时,代码会停住很久,并且这个时间长到我们无法接受,这就是因为函数fib在递归时,创建的函数栈帧太多了,一直递推,一直返回,并且还伴随着多个重复,导致代码卡在那里,如图:

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

运行结果为:

    算出的fib(50)的值超过了int的上限,所以出错了,但是后面count的计数没有问题,可以看到光是算个fib(50)居然就算了5亿多次fib(3),可见这个代码有多浪费空间,多没有效率,所以这种情况我们可以使用迭代替换,我们知道斐波那契数的前2个数都是1,然后前2个数相加就是第3个数,那么我们从前往后,从⼩到⼤计算就⾏了,如图:

    如果我们再次输入50让它计算,可以看到几乎瞬间就可以得到答案,虽然答案还是会因为超出int最大值而错误,但是至少我们知道这样运行效率很高

六、 递归拓展学习

  1. ⻘蛙跳台阶问题
  2. 汉诺塔问题

可以尝试自己解决,解析和答案在下期给出,敬请期待!

七、扫雷进阶思路

    在上一篇带着大家写了扫雷基本架构,算是简单实现了扫雷的玩法,但是还是有很多缺陷,比如不能标记,在排查坐标周围没有雷,不能扩展的等等问题     但是实际上现在我们已经有能力实现这些需求,比如标记,我们可以在用户排完坐标后进行询问是否标记雷,然后用某个符号代替标志,比如排查坐标周围没有雷时,可以进行扩展,这不就跟我们今天学习的递归紧密相连吗?将扩展一片没有雷的区域,化小为某个坐标扩展加上其它坐标扩展,反复递推,然后回归,我们学的递归就很有用了     现在我们学习了递归,在这里我给出思路,希望友友们可以通过自己的思考将扫雷篇章的那些扩展写出来,当然,我会在下一篇总体出一个扫雷进阶的实现,敬请期待吧!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-09-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、什么是递归
  • 二、递归的使用思路和限制条件
    • 1.递归的使用思路
      • 2.递归的限制条件
      • 三、递归的举例
        • 举例1:求n的阶乘
          • 2.举例2:顺序打印⼀个整数的每⼀位
          • 四、递归与迭代对比
          • 五、递归与迭代对比举例
          • 七、扫雷进阶思路
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档