C语言----函数递归(自我总结)

一.什么是递归?

递归是学习C语⾔函数绕不开的⼀个话题!

函数的递归:递归其实是⼀种解决问题的方法。在C语⾔中,递归就是函数自己调用自己。

举例:写一个史上最简单函数递归的代码:C语言----函数递归(自我总结)_第1张图片

此代码的运行结果如下: C语言----函数递归(自我总结)_第2张图片

以上代码就是⼀个简单的递归程序,只不过上述代码不是为了解决问题,而是为了演示递归的基本形式,代码陷⼊了递归死循环,最终导致栈溢出(Stack overflow)。

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

  • 递归的限制条件: 
  1. 递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。
  2.  每次递归调⽤之后越来越接近这个限制条件。

 函数递归的特点:用少量的代码,就能完成非常复杂的任务。

二. 递归举例

  • 例1:求n的阶乘。计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。

思路:把⼀个较⼤的问题,转换为⼀个与原问题相似,但规模较⼩的问题来求解。由于5! = 5*4*3*2*1  , 4! = 4*3*2*1  则 5! = 5*4! ;当 n==0 的时候,n的阶乘是1,其余n的阶乘都是可以通过公式计算。如下:C语言----函数递归(自我总结)_第3张图片

上述程序,假设Fact(n)就是求n的阶乘,那么Fact(n-1)就是求n-1的阶乘 。递归的条件:n>0;
递归停下来的条件:n == 0;(这⾥不考虑n太⼤的情况,n太大会导致栈溢出

  • 例2:顺序打印⼀个整数的每⼀位。输⼊⼀个整数m,按照顺序打印整数的每⼀位。 

思路:⾸先想到的是,怎么得到这个数的每⼀位呢? 如果m是⼀位数,m的每⼀位就是m自己 ;相反m是超过1位数的话,就得拆分每⼀位 。

比如:1234%10就能得到4,然后1314/10得到131,这就相当于去掉了4 然后继续对131%10,就得到了1,再除10去掉13,以此类推 不断重复 %10 和 /10 的操作,就可得到1314的每⼀位。但这⾥有个问题就是得到的数字顺序是倒着的,怎么才能顺序打印呢?

那我们假设想写⼀个函数Print来打印n的每⼀位。把Print(1314) 打印1314每⼀位,拆解Print(131)打印131的每⼀位,再打印得到的4 ;把Print(131) 打印131每⼀位,拆解为Print(12)打印12的每⼀位,再打印得到的3 。以此类推直到Print打印的数字变成⼀位数时,就不需要再拆分,递归结束。实现如下:

C语言----函数递归(自我总结)_第4张图片

三.递归与迭代

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

int Fact(int n)
{
    if (n == 0)
    {
        return 1;
    }
    else
        return n * Fact(n - 1);
}

虽然Fact函数可以输出正确的结果,但是在递归函数调用的过程中涉及⼀些运⾏时的开销。n如果太大的话就会导致栈溢出。

这是因为:在C语⾔中每⼀次函数调⽤,都需要为本次函数调调用在栈区申请⼀块内存空间来保存函数调⽤期间各种局部变量的值,这块空间被称为运⾏时堆栈,或称为函数栈帧。 函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次递归函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。 所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,随着递归层次越来越深就会引起栈溢出(stack overflow)的问题。

所以如果不想使⽤递归就得想其他的办法,通常就使用迭代的⽅式(把一件事情重复去做——通常就是循环的⽅式)。 

  • 那么求 n!用迭代的方式如何求呢?—— 也是可以采用循环1~n的数字累计乘在⼀起求的。实现如下:

C语言----函数递归(自我总结)_第5张图片

上述代码是能够完成任务,并且效率是⽐递归的⽅式更好的。

注:当⼀个问题非常复杂,难以使⽤迭代的⽅式实现时,此时递归实现的简洁性便可以补偿它所带来的运⾏时开销。有时候,写一个代码,虽然递归难以想到,但是使用递归写出的代码会非常的简单,往往一个代码使用递归写可能就几行代码,写成迭代的方式,就得十几行甚至几十行代码。。。
但是如果递归不恰当书写,会导致无法接受的后果,那我们还是放弃使用递归,使用迭代解决问题!

  • 求第n个斐波那契数。斐波那契数列(前两个数相加等于后一个数):1 1 2 3 5 8 13 21 34 55....

计算第n个斐波那契数,也是不适合使⽤递归求解的,但是斐波那契数的问题是可以通过递归的形式描述的,如下:C语言----函数递归(自我总结)_第6张图片

 

而当 n 输⼊为90的时候,如下:

C语言----函数递归(自我总结)_第7张图片

可见上述代码,需要很⻓时间才能算出结果,这个计算所花费的时间,是我们很难接受的, 这也说明递归的写法是⾮常低效的,那是为什么呢?

因为递归程序会不断的展开,在展开的过程中,我们很容易就能发现,在递归的过程中会有重复计 算,而且递归层次越深,冗余计算就会越多  ,从而计算所发费的时间也越多。我们可以作个测试:

C语言----函数递归(自我总结)_第8张图片

上述代码可看到,在计算第30个斐波那契数的时候,使⽤递归⽅式,第3个斐波那契数就被重复计算了 317811次,这些计算是⾮常冗余的。所以斐波那契数的计算,使⽤递归是⾮常不明智的,我们就得想迭代的⽅式解决。如下:C语言----函数递归(自我总结)_第9张图片

以上代码用迭代的⽅式去实现,输入90得出结果的效率就要⾼出很多了。(结果为负数是因为超出了int型取值范围,导致栈溢出了)

递归经典问题还有:汉诺塔问题  青蛙跳台阶问题(斐波那契数列问题)……
有时候,递归虽好,但是也会引⼊⼀些问题,所以我们⼀定不要迷恋递归,适可⽽⽌就好。

你可能感兴趣的:(c语言,c++)