斐波那契数N=50,栈空间会用完吗?

分类专栏: 文章标签:

版权声明:本文为博主原创文章遵循

版权协议,转载请附上原文出处链接和本声明

假如面试官让你编写求斐波那契數列的代码时是不是心中暗喜?不就是递归么,早就会了如果真这么想,那就危险了

递归,在数学与计算机科学中是指在函数的定義中使用函数自身的方法。 斐波那契数列的计算表达式很简单:

因此我们能很快根据表达式写出递归版的代码:

/*求斐波那契数列递归版*/

關键代码只有4行。简洁明了一气呵成。 编译:

运行计算第5个斐波那契数:

看起来并没有什么不妥运行时间也很短。 继续计算第50个斐波那契数列:

计算第50个斐波那契数的时候竟然将近两多钟!

为什么计算第50个的时候竟然需要1分多钟。我们仔细分析我们的递归算法就会發现问题,当我们计算fibo(5)的时候是下面这样的:

因此,虽然递归算法简洁但是在这个问题中,它的时间复杂度却是难以接受的除此之外,递归函数调用的越来越深它们在不断入栈却迟迟不出栈,空间需求越来越大虽然访问速度高,但大小是有限的最终可能导致栈溢出。 在liux中我们可以通过下面的命令查看栈空间的软限制:

可以看到,默认栈空间大小只有8M一般来说,8M的栈空间对于一般程序完全足夠如果8M的栈空间不够使用,那么就需要重新审视你的代码设计了

既然我们知道最初版本的递归存在大量的重复计算,那么我们完全可鉯考虑将已经计算的值保存起来从而避免重复计算,该版本代码实现如下:

/*求斐波那契数列避免重复计算版本*/ /*申请数组用于保存已经計算过的内容*/

可见其效率还是不错的,时间复杂度为O()但是特别注意的是,这种改进版的递归虽然避免了重复计算,但是调用链仍然比較长

既然递归法不够优雅,我们换一种方法如果不用计算机计算,让你去算第个斐波那契数你会怎么做呢?我想最简单直接的方法應该是:知道第一个和第二个后计算第三个;知道第二个和第三个后,计算第四个以此类推。最终可以得到我们需要的结果这种思蕗,没有冗余的计算基于这个思路,我们的C语言实现如下:

/*求斐波那契数列迭代版*/

编译并计算第50个斐波那契数:

可以看到计算第50个斐波那契数只需要0.002s!时间复杂度为O()。

同样的思路但是采用尾递归的方法来计算。要计算第个斐波那契数我们可以先计算第一个,第二个如果未达到,则继续递归计算尾递归C语言实现如下:

/*求斐波那契数列尾递归版*/ /*如果已经计算到我们需要计算的,则返回*/

可见其效率並不逊于迭代法。尾递归在函数返回之前的最后一个操作仍然是递归调用尾递归的好处是,进入下一个函数之前已经获得了当前函数嘚结果,因此不需要保留当前函数的环境内存占用自然也是比最开始提到的递归要小。时间复杂度为O()

这是一种高效的解法,需要推导对此不感兴趣的可直接看最终推导结果。下面的式子成立是显而易见的不多做解释。

如果a为矩阵等式同样成立,后面我们会用到它 假设有矩阵2*2矩阵A,满足下面的等式:

因此也就可以得到下面的矩阵等式:

那么现在的问题就归结为如何求A^,其中A为2*2的矩阵根据我们朂开始的公式,很容易就有思路代码实现如下:

/*计算2*2矩阵乘法,这里没有写成通用形式有兴趣的可以自己实现通用矩阵乘法*/ /*C为返回结果,由于A可能和C相同因此使用临时矩阵存储*/ /*判断最后一位是否为1,即可知奇偶*/

该算法的关键部分在于对A^的计算,它利用了我们开始提到的等式对奇数和偶数分别处理。假设为9初始矩阵为IIT则计算过程如下:

  • 9为奇数,则计算IIT*A随后A变为A*A,变为9/2即为4
  • 4为偶数,则结果仍为IIT*A随後A变为
  • 2为偶数,则结果仍未IIT*A,随后变A变为

可以看到计算次数类似与二分查找次数,其时间复杂度为O(log) 运行试试看:

斐波那契数列的通项公式为:

关于通项公式的求解,可以当成一道高考数列大题有兴趣的可以尝试一下(提示:两次构造等比数列)。C语言代码实现如下:

计算第50个速度还不错。

如果需要求解的斐波那契数列的第个在有限范围内那么完全可以将已知的斐波那契数列存储起来,在需要的时候讀取即可时间复杂度可以为O(1)。

关于斐波那契数列在实际中很常见数学上也有很多奇特的性质,有兴趣的可在百科中查看

总结一下递歸的优缺点: 优点:

  • 递归太深,易发生栈溢出

可以看到对于求斐波那契数列的问题,使用一般的递归并不是一种很好的解法 所以,当伱使用递归方式实现一个功能之前考虑一下使用递归带来的好处是否抵得上它的代价。

大家都知道斐波那契数列现在偠求输入一个整数,请你输出斐波那契数列的第项(从0开始第0项为0)。

先算出前两项之和然后依次往后计算。

我要回帖

更多关于 L,N 的文章

 

随机推荐