1. 程式人生 > >斐波那契數列演算法優化問題

斐波那契數列演算法優化問題

斐波那契是數學中最值得討論的一個問題,從12世紀斐波那契提出這個數列後,就有很多數學家研究過這個數列,對斐波那契數列的新發現也越來越多,這些細節我沒能力去研究,這篇文章中要講的是程式設計中對生成斐波那契數演算法的優化。首先要說的就是斐波那契數列的定義,這一切都起源於一個生殖能力超強的兔子:

  • 第一個月初有一對剛誕生的兔子
  • 第二個月後(第三個月初)他們可以生育
  • 每月沒對兔子可生育的兔子會誕生下一對新兔子
  • 兔子永不死去

幾乎每個學計算機的在學程式語言的時候都會遇到這樣的習題:計算第N個月兔子的總數

點選這裡檢視完整原始碼,建議對著完整的程式碼除錯。

最簡單的遞迴演算法

老師肯定會教的一種方法:

uint64_t fibonacci(unsigned int n) {
    if (n == 0) return 0;
    if (n <= 2) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

該方法來自於斐波那契數列的一個遞推式:fib(n) = fib(n-1) + fib(n-2)

然後使用遞迴演算法並指定遞迴出口,即可得出結果。

使用迴圈迭代消除遞迴

遞迴因為要不斷地呼叫函式自身,呼叫函式就伴隨著引數以及函式區域性變數入棧,當遞迴層數較大容易產生棧溢位,所以通常需要我們使用迴圈優化遞迴演算法。幸運地是,大多數遞迴都能修改成迴圈(使用自定義棧儲存變數的方式仍然算遞迴)。而且上面的演算法在效率上存在很大的優化空間:

斐波那契數列

你會發現fib(5) = fib(4) + fib(3),而求fib(4)的時候我們已經求過fib(3),這意味著我們做了很多重複的工作,很明顯我們需要把前面做過的工作暫存。

遞迴演算法時間呈指數形式增長:O(2^N);而使用迴圈迭代時間上呈線性增長:O(N)。在我筆記本上測試時,當n超過40遞迴演算法的時間就開始爆炸了。

uint64_t fibonacci(unsigned int n) {
    if (n == 0) return 0;
    if (n == 1 || n == 2) return 1;
    uint64_t f1 = 1, f2 = 1, fn;
    for
(unsigned int i = 3; i <= n; i++) { fn = f1 + f2; f1 = f2; f2 = fn; } return fn; }

矩陣演算法求解

斐波那契數列的遞推公式是:fib(n) = fib(n-1) + fib(n-2);我們可以用矩陣來表示這種關係:

[FnFn1]=[Fn1Fn2Fn1]=[1110]×[Fn1Fn2]
進一步推到可以得到:
[FnFn1]=[1110]n1×[F1F0]=[1110]n1×[10]

從0開始算得到Fn則需要更進一步:

[Fn+1Fn]=[1110]n×[F1F0]=[1110]n×[10]

我們要實現一下矩陣運算: