1. 程式人生 > >斐波那契數列的各種演算法實現

斐波那契數列的各種演算法實現

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

Version 1

long 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); 
  else 
       return -1; 
}

這是最基本的遞迴思路,大家的第一個斐波那契數列應該都是寫成這樣的,但是不知道大家有沒有測試過它的效能如何,不測不知道,一測嚇一跳,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}; 

long Fibonacci2(int n) 
{ 
  if (n == 0) 
     return 0; 
  else if (n == 1) 
    return 1; 
  else if (n > 1) 
  { 

    if(tempResult[n] != 0) 
      return tempResult[n]; 
    else 
    { 
          tempResult[n] = Fibonacci2 (n - 1) + Fibonacci2 (n - 2); 
          return tempResult[n]; 
     } 
   } 
} 

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

  temp[0] = 0; 

  if (n > 0) 
     temp[1] = 1; 

   for(int i = 2; i <= n; ++i) 
  { 
     temp[i] = temp[i - 1] + temp[i - 2]; 
  } 

  long result = temp[n]; 

  delete[] temp; 

  return result; 
}

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