1. 程式人生 > >斐波那契數列的遞迴與迴圈的演算法實現

斐波那契數列的遞迴與迴圈的演算法實現

斐波那契數列,但凡學過程式設計的童鞋們應該都懂,背景就不介紹了(就是大兔子生小兔子的故事),無論是面試還是實際的運用,常見的一個思路就是先用最先基本的辦法實現,然後根據實際要求,一步步改進,優化演算法效率。今天就以斐波那契數列這個大家都很熟悉的為例來小小感受一下。

  1. Version 1
  2. long Fibonacci(int n)   
  3. {   
  4.   if (n == 0)   
  5.       return0;   
  6.   elseif (n == 1)   
  7.       return1;   
  8.   elseif (n > 1)   
  9.        return Fibonacci (n - 
    1) + Fibonacci (n - 2);   
  10.   else
  11.        return -1;   
  12. }  

這是最基本的遞迴思路,大家的第一個斐波那契數列應該都是寫成這樣的,但是不知道大家有沒有測試過它的效能如何,不測不知道,一測嚇一跳,N=1000,500,100,50都是黑視窗半天跳不出資料,看CPU是100%(我的電腦是25%,但是因為我的電腦是4核的,大家懂的),最後N=45得到的結果是145秒。 效能為什麼會這麼低呢?以N=5為例子,該程式的執行過程如下圖所示,可以想象,N到一定的數目(其實還是很小的數目,比如45.。。。)就會有很多次的遞迴調

 

那麼,自然的想法是,有什麼辦法可以減少遞迴的次數呢?再觀察上圖,可以看出,有重複的遞迴呼叫。比如F(3)就計算過兩次,一個解決的方法就是記錄下來已經得到結果的F(n),避免重複多次計算。

 Version 2  long tempResult[10001]={0}; 

  1. long Fibonacci2(int n)   
  2. {   
  3.   if (n == 0)   
  4.      return0;   
  5.   elseif (n == 1)   
  6.     return1;   
  7.   elseif (n > 1)   
  8.   {   
  9.     if(tempResult[n] != 0)   
  10.       return tempResult[n];   
  11.     else
  12.     {   
  13.           tempResult[n] = Fibonacci2 (n - 1) + Fibonacci2 (n - 
    2);   
  14.           return tempResult[n];   
  15.      }   
  16.    }   
  17. }   

這次優化之後,效率明顯提高,N=1000時,執行時間仍趨近於0秒,但是當N=5000時出現了棧溢位的情況。再來分析,棧溢位,那麼棧當中有什麼呢?呼叫資訊,變數。我們version2的改進,是將version1的呼叫樹砍掉了一半,所以,要真正解決這個問題,還是要放棄遞迴演算法。大家應該瞭解,常見的改變遞迴演算法的方式是將它變成迴圈。實際上,遞迴是從大往小分解問題,迴圈則是反方向演算法。
  1. long Fibonacci3(int n)   
  2. {   
  3.   long * temp = newlong[n + 1];   
  4.   temp[0] = 0;   
  5.   if (n > 0)   
  6.      temp[1] = 1;   
  7.    for(int i = 2; i <= n; ++i)   
  8.   {   
  9.      temp[i] = temp[i - 1] + temp[i - 2];   
  10.   }   
  11.   long result = temp[n];   
  12.   delete[] temp;   
  13.   return result;   
  14. }  

現在,當N=1000000的時候,時間仍然小於1秒了。 當然,問題還沒有結束,雖然version3看上去已經是一個效率很好的演算法了。前面的解決方式都是自然的從演算法角度來考慮,但是,數學的力量是偉大的。version3的複雜度是O(n),有沒有對數級的演算法,或者更好的,常量時間演算法呢? 迴歸到高中數學,發現f(n)=f(n-1)+f(n-2)是一個數列的通項公式,經過化簡,我們可以得到它的遞推公式,可以一步得出結果,為O(1)的時間複雜度。但是由於最後的遞推公式中含有無理數,所以不能保證結果的精度。  

斐波那契數列:兔子問題及其應用

斐波那契(Fibonacci1170-1250),義大利最傑出的數學家。其父為比薩的商人,他認為數學是有用的,因此送斐波那契向阿拉伯教師們學習數學,掌握了印度數碼之一新的記數體系,後來遊歷埃及、敘利亞、希臘、西西里、法國等地,掌握了不同國家和地區商業的算術體系,1200年回答比薩,潛心研究數學,1202年寫成《算盤全集》,此書廣為流傳,為在歐洲傳播印度-阿拉伯數碼起了重要的作用。

1228斐波那契在修訂《算盤全集》修訂本中,增加了一道非常有名的兔子繁殖問題,問題是這樣的:如果一對兔子每月生一對兔子;一對新生兔,從第二個月起就開始生兔子;假定每對兔子都是一雌一雄,試問一對兔子,一年能繁殖成多少對兔子?

先看前幾個月的情況:第一個月有一對剛出生的兔子,即F(1)=1;第二個月,這對兔子長成成年兔,即F(2)=1;第三個月,這對成年兔生出一對小兔,共有兩對兔子,即F(3)=2;第四個月,成年兔又生出一對小兔,原出生的兔子長成成年兔,共有三對兔子,即F(4)=3;第五個月,原成年兔又生出一對小兔,新成年兔也生出一對小兔,共有五對兔子,即F(5)=5;……以此類推,可得每個月的兔子對數,組成數列:1,1,2,3,5,8,13,21,34,55,89,144,…,這就是著名的斐波那契數列,其中的任一個數,都叫斐波那契數。

題中本質上有兩類兔子:一類是能生殖的兔子,稱為成年兔子;新生的兔子不能生殖;新生兔子一個月就長成成年兔子。求的是成年兔子與新生兔子的總和。每月新生兔對數等於上月成年兔對數。每月成年兔對數等於上個月成年兔對數與新生兔對數之和。最後得關係式:

F(1)=F(2)=1;

F(n)=F(n-1)+F(n-2) (n≥3)。

法國數學家比內(Binet)證明了通項公式為

斐波那契數列:兔子問題的c++實現演算法

#include<iostream>
using namespace std;
int main()
{
 int i,a[30]={0,1,1};//陣列的定義,用來儲存資料
 for(i=3;i<30;i++)
 {
  a[i]=a[i-1]+a[i-2];//Fibonacci的算術方程
 }
 for(i=1;i<30;i++)
 {
  printf("a[%d]=%d\n",i,a[i]);//輸出
 }
}



C++基礎譚浩強書裡的程式

#include<iostream>
#include<iomanip>
using namespace std;
int main()
{
 long f1,f2;
 int i;
 f1=f2=1;
 for(i=1;i<20;i++)
 {
  cout<<setw(12)<<f1<<setw(12)<<f2;
  if(i%2==0)cout<<endl;
  f1=f1+f2;
  f2=f2+f1;
 }
 return 0;
}