斐波那契(Fibonacci)數列

以下取自《編程之美-微軟技術面試心得》

斐波那契數列由如下遞推關係式定義:

F(0)=0 F(1)=1

F(n)=F(n-1)+F(n-2) if n>1

求解斐波那契數列的方法有以下幾種:


1.根據遞推公式可以很容易想到用遞歸的方法求解第n+1項的值,代碼如下:

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

這種方法在計算F[n]時,需要計算從F[2]到F[n-1]每一項的值,這樣簡單的遞歸式存在着很多的重複計算,如求F[5]=F[4]+F[3],在求F[4]的時候也需要求一次F[3]的大小,等等。


2.爲了減少上述的重複運算,可以用一個數組存儲所有已經計算過的項。這樣便可以達到用空間換時間的目的。在這種情況下,時間複雜度爲O(N),而空間複雜度也爲O(N)。

int Fibonacci3(int n)
{


   int *res = new int[n+1];
   res[0]=0;
   res[1]=1;
   int i=0,result=0;
   if(n<=0)
  return 0;
   if(n==1)
  return 1;
   for(i=2;i<=n;i++)
   {
res[i]=res[i-1]+res[i-2];
   }
   result=res[n];
   delete[] res;
   return result;
}

該方法是根據編程之美書中題意實現的,申請了數組,空間複雜度爲o(n);

但其實有空間複雜度更小的解法,只保存之前兩項的值爲x和y,計算待求的z後更新x和y,這樣空間複雜度就減少了。代碼如下:

int Fibonacci2(int n)
{
   int i=0,x=0,y=1,z=0;
   if(n<=0)
  return 0;
   if(n==1)
  return 1;
   for(i=2;i<=n;i++)
   {
  z=x+y;
  x=y;
  y=z;
   }
   return z;
}






3.如果我們知道一個數列的通項公式,利用公式來計算會更加容易。能不能把這個函數的遞推公式計算出來呢?

由遞推公式F(n)=F(n-1)+F(n-2),知道F(n)的特性方程爲:x^2=x+1(見補充)

有根:x1=(1+sqrt(5.0))/2,x2=(1-sqrt(5.0))/2

所以存在A,B使得:

F(n)=A*x1^n+B*x2^n

代入F(0)=0,F(1)=1,解得A=1/sqrt(5.0),B=-1/sqrt(5.0),即

F(n)=(x1^n-x2^n)/sqrt(5.0)

通過公式,我們可以在O(1)的時間內得到F(n)。但公式引入了無理數,所以不能保證結果的精度。


4.注意到Fibonacci數列是二階遞推數列,所以存在一個2*2的矩陣A,使得:

(F[n] F[n-1])=(F[n-1] F[n-2])*A,結合F[n]=F[n-1]+F[n-2]求解,可得:

A=1 1,1 0(上面是1 1,下面爲1 0),由上面的矩陣遞推公式有:

(F[n] F[n-1])=(F[n-1] F[n-2])*A=(F[n-2] F[n-3])*A^2=...=(F[1] F[0])*A[n-1],或

斐波那契(Fibonacci)數列 - chhaj5236 - chhaj5236的博客

剩下的問題就是求解矩陣A的方冪。A^n=A*A*...*A,最直接的解法就是通過n-1次乘法得到結果。但是當n很大時,比如1 000 000或1 000 000 000,這個算法的效率就不能接受了。當然,在這個情況下,F[n]在int所表示的整數裏早就溢出了,但如果要求解的是F[n]對某個素數的餘數時,這個算法會是非常有用和高效的。

注意到:

A^(x+y)=A^x*A^y

A^(x*2)=A^(x+x)=(A^x)^2

用二進制方法表示n:

n=ak*2^k+a(k-1)*2^(k-1)+...+a1*2+a0(其中ai=0或1,i=0,1,...,k)

A^n=A^(ak*2^k+a(k-1)*2^(k-1)+...+a1*2+a0)=(A^(2^k))^ak*...*A^a0

如果能夠得到A^(2^i)的值,就可以再經過logn次乘法得到A^n.

而這容易通過遞推得到:

A^(2^i)=(A^(2^(i-1)))^2。根據上面的思想,具體代碼如下:

Class Matrix;                                               //假設我們已經有了實現乘法操作的矩陣類
Matrix MatrixPow(const Matrix &m,int n){    //求解m的n次方
    Matrix result=Matrix::Identity;                 //賦初值爲單位矩陣
    Matrix tmp=m;
    for(;n;n>>=1){
        if(n&1)result*=tmp;
        tmp*=tmp;
    }
}
int Fibonacci(int n){
    Matrix an=MatrixPow(A,n-1);                    //A的值就是上面求解出來的
    return F1*an(0,0)+F0*an(1,0);
}

整個算法的時間複雜度爲O(logn)


解法3其實就是用到數學公式求得的矩陣A,求其n次冪時用了分冶的策略,減少了運算次數





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章