【C语言】函数的递归

目录

一、什么是递归

二、递归的思想

三、递归的限制条件

四、递归中的栈溢出

五、递归举例

(1)例1:n的阶乘

(2)例子2:顺序打印一个数的每一位

六、递归和迭代

七、拓展题目

(1)青蛙跳台阶问题

(2)汉诺塔问题


一、什么是递归

        递归就是函数自己调用自己。下面举一个简单的递归例子(并不能解决问题,用法也不正确,只是让大家感受一下递归是什么样的):

        在打印了很多次后终止了运行,但预期是一直死循环打印下去,为什么会这样?调试看看:

        用F11进行调试,不进入第6行的main();,最后弹出了一个错误,如上图,Stack overflow(栈溢出),出现这个问题的原因在第四节讲。

二、递归的思想

        将一个复杂的问题分解为相似的子问题,子问题再分解为更小的相似子问题,直到子问题不能再分解就结束分解。递归的递,表示递推,就是分解的过程;递归的归,表示回归,结束分解后再一个个返回子问题计算的结果,从小问题的解决到最后整个复杂问题的解决。

三、递归的限制条件

        递归必须有以下两个条件:

  • 递归必须有限制条件,这是递归中递推过程结束的条件。
  • 每一次递归,要离限制条件更近。

四、递归中的栈溢出

        在C语言中每次调用函数,都会向栈区申请一块内存空间,用于存放函数调用期间的各种信息(包括调用函数里局部变量的值等),这块空间被称为 运行时堆栈函数栈帧

        每一次递归都会为调用的函数申请一块栈帧空间,只有函数返回后,才会释放对应的栈帧空间。如果递归太深而不返回,就会导致内存不够,发生栈溢出的问题。

        这也是为什么递归必须要有限制条件的原因。

五、递归举例

(1)例1:n的阶乘

        题目:计算n的阶乘(不考虑溢出,数太大了会发生溢出),n的阶乘就是1~n的数字累积相乘。

        分析:

        Fact(n)是求解n阶乘的递归函数,Fact(n - 1)是求解n - 1阶乘的递归函数,也是求解n阶乘的相似子问题。n等于0时,0的阶乘为1,是该递归函数的限制条件

        代码及运行结果:

        递归过程图(红色是递推过程,蓝色是回归过程,绿色是返回的结果):

(2)例子2:顺序打印一个数的每一位

        题目:输入一个整数m,按照顺序打印m的每一位数。

        如:输入1234,打印1 2 3 4。

        分析:对于数字n,使用n%10可以得到最后一位数。

        9 > m ≥ 0时打印m,就是该递归的限制条件;Fact(m/10)就是相似子问题。这道题中的递归没有返回值,而是把返回值改为了打印数字。因为当m为个位数时,也可以用m%10表示,所有在代码中可以把两个条件下的打印部分统一在一起。

        代码及运行结果:

六、递归和迭代

        递归非常好用,可以把一个复杂的问题,以很简单的代码解决,但是它也有缺陷,在第四节讲的递归太深会造成很大的运行开销(无论是时间还是空间)。所以当一个问题用递归的方法很好解决,但是对于运行时的开销有很大的缺陷,递归的好用已经弥补不了这个缺陷时,就需要用迭代的方法(通常是循环的方式)解决了。

        递归运行开销大这个缺陷,在斐波那契数列这道题中显著体现。

        题目:求第n个斐波那契数。第一、二个数是1,后面的数都是它前两数的和。如:1,1,2,3,5......

        ① 递归的方法:

        分析:

        这道题可以用很简单的递归方法解决。

        代码及运行结果:

        当n较小时,比如10,很快计算出了结果。

        当n较大时,比如50,计算特别慢,等了很久都没有结果出来。大家可以试试。

        讨论一下运行慢的原因:

        简略画一下计算第50个斐波那契数的递归过程图:

        从上面的图可以看到有很多重复(同色)的计算,这个图只是画了一点,全部延伸下去直到遇到1和2才结束递推,可想而知要计算很多很多很多很多次。

        用小点的n = 40做例子,看看计算了多少次Fact(3):

        可以看到仅仅对于比较小的数40,它的Fact(3)就计算了这么多次!

        所以这道题我们不能使用递归的方法,它的运行开销实在太大。改用循环的方法:

        计算n=50,结果立马就运行出来了。注:把a、b、c换成long long类型是因为计算结果太大了,定义为int类型会溢出,看不到正确结果。仔细思考一下,这个计算时间只要50-2=48次循环而已。对比一下,用递归法算n=40,光计算Fact(3)都计算了39088169次。

七、拓展题目

(1)青蛙跳台阶问题

        题目:一只青蛙一次可以跳1个或者2个台阶,请计算它跳n个台阶有几种跳法。

        分析:

        如果跳n个台阶,最后一跳要么是已经跳了n-1个台阶,最后一次跳一个台阶;要么是已经跳了n-2个台阶,最后一次跳两个台阶。Fact(n)表示跳n阶的跳法种数,可以拆分成 “最后跳一个台阶:跳n-1阶的跳法种数Fact(n-1)” 加上 “最后跳两个台阶:跳n-2阶的跳法种数Fact(n-2)”。对于限制条件(因为n=3的时候要算Fact(1)和Fact(2),所以限制条件为n=1和n=2两种情况):跳一个台阶时,只有一种跳法;跳两个台阶时,有两种跳法(跳两次一阶,或者跳一次两阶)。

        代码及运行结果:

(2)汉诺塔问题

        题目:如下图,有ABC三个杆,A杆上有n个盘子,请计算把A杆上的n个盘子全部移到C杆上,需要移动多少次盘子。

        移动规则:

  • 一次只能移一个盘子。
  • 大盘子不能放在小盘子上。
  • 可以以B杆作为中介柱子。

        分析:

        首先,若只有一个盘子,需要移动1次(A>C);若只有两个盘子,需要移动3次(小:A>B,大:A>C,小:B>C)。而n(n>1)个盘子,则可以类比移动两个盘子,看作上面有n-1个盘子和下面有一个盘子。因为最底下那个盘子最大,所以上面n-1个盘子中,随便哪个盘子都可以在它的上面。因此,上面的n-1个盘子移动时,可以把最底下的那个盘子忽略掉。【上面n-1整体:A>B,移动Fact(n-1)次;最大:A>C,移动1次;上面n-1整体:B>C,移动Fact(n-1)次】,加起来就是1+2*Fact(n-1)次。

        为什么不把下面的n-1个作为整体,最小的看作另一个整体呢?如果把最小的看作一部分,下面的n-1个看作一部分,n-1个盘子移动时,就不能忽略掉最小的那个,【最小:A>B,移动1次;n-1整体:A>C,移动Fact(n-1)次;最小:B>C,移动一次】这个想法也就是错误的。因此,不能用Fact(n) = 2 + Fact(n-1)做递归。

        代码及运行结果:

  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值