C语言递归算法

目录

递归

什么是递归?

递归的两个必要条件 

递归的优缺点

递归求阶乘

递归求斐波那契数

优化求阶乘和斐波那契数

总结


递归

什么是递归?

所谓递归,我认为就是存在传递也存在归还功能的一种算法,简单点来说,就是一个函数直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。特点:函数自己调用自己

递归的两个必要条件 

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

递归的优缺点

优点:

  1. 简洁。只需要几行代码就能实现递归。
  2. 在树的前序,中序,后序遍历算法中,递归的实现明显要比循环简单得多。


缺点:

  1. 递归由于是函数调用自身,而函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址以及临时变量,而往栈中压入数据和弹出数据都需要时间。->效率
  2. 递归中很多计算都是重复的,由于其本质是把一个问题分解成两个或者多个小问题,多个小问题存在相互重叠的部分,则存在重复计算,如Fibonacci斐波那契数列的递归实现。->效率
  3. 调用栈可能会溢出,其实每一次函数调用会在内存栈中分配空间,而每个进程的栈的容量是有限的,当调用的层次太多时,就会超出栈的容量,从而导致栈溢出。->性能

下面从几个递归的例题让大家更清楚的理解递归算法!

递归求阶乘

  • 函数功能:设计factorial函数求第n阶的阶乘数。(不考虑溢出)
  • 递归部分:阶乘的原公式是:n!=n*(n-1)*(n-2)...3*2*1,根据将大问题分解成小问题,我们把它转换成这样:n!=n*(n-1)!
  • 核心代码:
int factorial(int n)
{
    if(n <= 1)// 0! = 1
        return 1;
    else
        return n* factorial(n-1)
}

当n=5时 Factorial函数递归过程配图如下:

C语言递归算法_第1张图片


递归求斐波那契数

普及一下,斐波那契数列(Fibonacci Sequence)又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……

  • 函数功能:设计Fibonacci函数求第n个斐波那契数的值。(不考虑溢出)
  • 递归部分:F(n)=F(n-1)+F(n-2)(n>2,n∈N*,F[1]=1,F[2]=1),也就是一个数会等于前两个数相加的结果。
  • 核心代码:
int fib(int n)
{
    if(n <= 2)
        return 1;
    else
        return fib(n - 1) + fib(n - 2)
}

当n=6时,Fibonacci函数递归过程配图如下:

在这里插入图片描述


从以上两个例题我们不难看出有些问题

  • Factorial函数递归会随着n的增加而使栈的空间变小,以至于栈溢出,程序崩溃。
  • Fibonacci函数递归会存在很多重复的计算,使得计算起来特别耗费时间。

递归与栈的关系

递归的过程就是出入栈的过程,在调试factorial函数的时候,如果你给的参数比较大,那就会报错:Stack Overflow(栈溢出)这样的信息。系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出

那如何优化解决上述的问题呢?

  1. 将递归改写成非递归。
  2. 使用static对象替代nonstatic局部对象。在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。

优化求阶乘和斐波那契数

  • 非递归的核心代码:
//求n的阶乘(非递归)
int factorial(int n)
{
    int result = 1;
    while(n > 1)
    {
         result *= n;
         n -= 1;
    }
    return result;
}
//求第n个斐波那契数(非递归)
int fib(int n)
{
    int result;
    int pre_result;
    int next_older_result;
    result = pre_result = 1;
    while(n > 2)
    {
          n -= 1;
          next_older_result = pre_result;
          pre_result = result;
          result = pre_result + next_older_result;
    }
    return result;
}

总结

  1. 解决递归问题的主要思想是分治思想,即我们在递归时要尽量将问题的规模缩小。
  2. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
  3. 但是这些问题的迭代实现往往比递归实现的效率更高,虽然代码的可读性稍微差些。
  4. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销

看到这里你应该对于递归有了很深的理解吧!递归的学习是一个漫长的过程,毕竟大师 L. Peter Deutsch 说过:To Iterate is Human, to Recurse, Divine.中文译为:人理解迭代,神理解递归。下篇我将会带领大家研究几个经典的递归问题。

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

如有补充或存在问题请在评论区留言哟~

你可能感兴趣的:(递归算法,算法,c语言)