斐波那契數列的各種演算法實現
阿新 • • 發佈:2019-01-02
斐波那契數列,但凡學過程式設計的童鞋們應該都懂,背景就不介紹了(就是大兔子生小兔子的故事),無論是面試還是實際的運用,常見的一個思路就是先用最先基本的辦法實現,然後根據實際要求,一步步改進,優化演算法效率。今天就以斐波那契數列這個大家都很熟悉的為例來小小感受一下。
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)的時間複雜度。但是由於最後的遞推公式中含有無理數,所以不能保證結果的精度。