斐波那契数列

来源:互联网 发布:c语言学生选课管理系统 编辑:程序博客网 时间:2024/06/05 11:08

微软面试题之一,难度系数中。

题目描述如下:

定义Fibonacci 数列如下: 
/ 0 n=0 
f(n)= 1 n=1 
\ f(n-1)+f(n-2) n=2 
输入n,用最快的方法求该数列的第n 项。

逻辑分析:

1、经典的斐波那契数列,故事背景就不介绍了,不了解的看客,面壁之前,自行百度脑补。

对于斐波那契数列问题,经常在各种语言书中,作为递归的例子,所以提起递归,很多程序员都会想到斐波那契数列,至于效率嘛。。。呵呵,你懂的,顺便一提,这货也是让大部分人不假思索,直言一切递归低效率的罪魁祸首。

这里给出坑爹的递归算法实现源码:

int Fibonacci(int n){if(n == 0)return 0;else if(n == 1)return 1;else if(n > 1)return Fibonacci(n-1)+Fibonacci(n-2);elsereturn -1;}

该算法效率低下的本质原因在于重复计算,我们以n=10为例,

                  f(10)
                      \
            f(9)         f(8)
          /     \          \
       f(8)     f(7)  f(7)   f(6)
        \     /   \
 
   f(7)  f(6)  f(6) f(5)

可以看出,树中的所有节点都需要计算,而显而易见的是,树中存在重复节点,树的高度为n-1,则时间复杂度为O(2^n)(等比数列求和)。

补充:这里的计算是估算,因为并非是满二叉树,严格来讲,这里只是得到了算法的复杂度阶——指数复杂度。

这里摘出数据结构经典书籍中的一段分析:

我们令T(N)为函数fib(n)的运行时间,当N>=2的时候我们分析可知:

T(N) = T(N-1) + T(N-2) + 2

fib(n) = fib(n-1) + fib(n-2)所以有T(N) >= fib(n)归纳法证明可得

fib(N) < (5/3)^N

N>4时,fibN>= (3/2)^N

标准写法:

显然这个O(3/2)^N 是以指数增长的算法基本上是最坏的情况。

其实,这违反了递归的一个规则:合成效益法则。

合成效益法则(Compound interest rule):在求解一个问题的同一实例的时候,切勿在不同的递归调用中做重复性的工作。

所以在上面的代码中调用fib(N-1)的时候实际上同时计算了fib(N-2)。这种小的重复计算在递归过程中就会产生巨大的运行时间。



2、找到了问题的所在,那么我们的想法,显然就是避免重复计算,实际上,不难想到,只要利用一个循环,不断递推,便可以在O(n)的时间复杂度内完成算法。

int Fibonacci(int n){int result[2] = {0,1};if(n<2)return result[n];int fibOne = 1,fibTwo = 0;int fibN = 0;for(int i=2;i<=n;i++){fibN = fibOne + fibTwo;fibTwo = fibOne;fibOne = fibN;}return fibN;}

3、题目要求时间复杂度尽可能低,而作为面试题,显然O(n)并非最佳解。到目前为止,我们成功避免了重复计算,那么,如果我们想进一步降低时间复杂度,就要从数学上删繁就简,得到一个最纯粹的式子,而对于数列来说,只要写出通项公式,那么对于计算机来说,时间复杂度基本上可以看做是O(1),斐波那契数列通项公式的求法,这里就不再多说,对于只要有一点高中数学竞赛基础的看客,想必并非难事。



代码实现:

int Fibonacci(int n){double z = sqrt(5.0);double x = (1 + z)/2;double y = (1 - z)/2;return (pow(x, n) - pow(y, n))/z;}

注:实际上,虽然理论上时间复杂度是O(1),不过效率上还是和具体的pow,sqrt函数有关系。


4、我想对于大部分人来说,问题做到这里,就可以结题了。不过对于斐波那契数列问题,一部分人是了解一种矩阵算法的(我记着好像是在MIT算法导论公开课看到的。。。)

我们将数列写成:

Fibonacci[0] = 0,Fibonacci[1] = 1

Fibonacci[n] = Fibonacci[n-1] + Fibonacci[n-2] (n >= 2)

可以将它写成矩阵乘法形式:

递推得到:

那么,我们的目标,就是计算

问题的精髓便在于此处,我们分离出了目标,但是直观上需要计算n次方,单纯的循环计算,时间复杂度上并不会有所改善,依然是O(n),而对于乘方计算这一问题,实际上也是历史悠久,没错,你应该想到了,利用二分法,算法五大思想中的分治。

乘方性质:

         a^(n/2)*a^(n/2)                          n为偶数时
a^n=
         a^((n-1)/2)*a^((n-1)/2)*a            n为奇数时

求n次方,那么先求得n/2次方,再将结果平方一下,所以,时间复杂度O(logn)。

#include <stdio.h>#include <math.h>//2x2矩阵结构体定义struct Matrix2By2{Matrix2By2(    int m00 = 0, int m01 = 0, int m10 = 0, int m11 = 0):m_00(m00), m_01(m01), m_10(m10), m_11(m11) {}int m_00;int m_01;int m_10;int m_11;};//矩阵乘法运算Matrix2By2 MatrixMultiply( const Matrix2By2& matrix1,  const Matrix2By2& matrix2 ){return Matrix2By2(matrix1.m_00 * matrix2.m_00 + matrix1.m_01 * matrix2.m_10,matrix1.m_00 * matrix2.m_01 + matrix1.m_01 * matrix2.m_11,matrix1.m_10 * matrix2.m_00 + matrix1.m_11 * matrix2.m_10,matrix1.m_10 * matrix2.m_01 + matrix1.m_11 * matrix2.m_11);}/////////////////////////////////////////////////////////////////////////计算矩阵的n次方// 1  1// 1  0///////////////////////////////////////////////////////////////////////Matrix2By2 MatrixPower(unsigned int n){Matrix2By2 matrix;if(n == 1){matrix = Matrix2By2(1, 1, 1, 0);}else if(n % 2 == 0){matrix = MatrixPower(n / 2);matrix = MatrixMultiply(matrix, matrix);}else if(n % 2 == 1){matrix = MatrixPower((n - 1) / 2);matrix = MatrixMultiply(matrix, matrix);matrix = MatrixMultiply(matrix, Matrix2By2(1, 1, 1, 0));}return matrix;}//斐波那契调用接口int Fibonacci(unsigned int n){int result[2] = {0, 1};if(n < 2)return result[n];Matrix2By2 PowerNMinus2 = MatrixPower(n - 1);return PowerNMinus2.m_00;}int main(){printf("%d",Fibonacci(10));return 0;}




0 0