<br />
斐波那契數列,(意大利語:Successione di Fibonacci),又譯做費波拿契數列、費氏數列、黃金分割數列。發明者,是意大利數學家列昂納多·斐波那契(Leonardo Fibonacci)。
斐波那契數列指的是這樣一個數列:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, ...
在數學上,斐波那契數列是以遞歸的方式定義:
- f(0) = 0
- f(1) = 1
- f(n) = f(n - 1) + f(n - 2) // n >= 2
1202年,斐波那契完成了巨著《計算之書》(Liber Abaci),斐波那契數列便是出自這本著作,它來自一個“兔子繁殖”問題:
假定兔子在出生兩個月后就有繁殖能力,一對兔子每個月能生出一對小兔子。如果所有兔子都不死,那么一年后可以繁殖多少對兔子?
兔子繁殖
程序員的信仰
作為一個Programmer,多年經驗給我的印象,我們不是純的數學家,我們也解決數學問題,并用數學的知識武裝自己。然而有時候,我們還要考慮一些非純數學的問題。比如性能,維護,可擴展,...
接下來,我們不再過多的關注斐波那契數列的歷史,和其在物理化學上的貢獻,把關注點集中在編程實現上。
第一個fabonacci程序
根據fabonacci的定義,我們可以很輕松的編寫出實現代碼:
function fabonacci(n) {
if (n === 0) {
return 0;
}
if (n === 1) {
return 1;
}
return fabonacci(n - 1) + fabonacci(n - 2);
}
讓我們來測試一下所編寫的程序,測試計算第50個值fabonacci(50):
var start = new Date();
var result = fabonacci(50);
var end = new Date();
console.log('fabonacci(%d) = %d, use time %dms.',
50,
result,
end.getTime() - start.getTime());
打開Shell,運行Node.js:
$ node
然后,復制上面的代碼,回車,得到如下的控制臺輸出:
> fabonacci(50) = 12586269025, use time 242702ms.
天啊!
在我的筆記本上計算fabonacci(50),竟然花費了242秒(4分鐘)!
一定是出現了什么問題!
讓我們仔細的推導整個計算過程,來找出潛在的問題:
* f(0) = 0
* f(1) = 1
* f(2) = f(1) + f(0)
* f(3) = f(2) + f(1)
= (f(1) + f(0)) + f(1)
* f(4) = f(3) + f(2)
= (f(2) + f(1)) + (f(1) + f(0))
= ((f(1) + f(0)) + f(1)) + (f(1) + f(0))
* f(5) = f(4) + f(3)
= (f(3) + f(2)) + (f(2) + f(1))
= ((f(2) + f(1)) + (f(1) + f(0))) + ((f(1) + f(0)) + f(1))
= (((f(1) + f(0)) + f(1)) + (f(1) + f(0))) + ((f(1) + f(0)) + f(1))
* ...
看出來了嗎?
- 當我們計算f(0)的時候,計算了1次f(0) => f(0)
- 當我們計算f(1)的時候,計算了1次f(1) => f(1)
- 當我們計算f(2)的時候,計算了1次f(1) + f(0) => f(2)
- 當我們計算f(3)的時候,根據遞歸過程,實際計算了
- 1次f(1) + f(0) => f(2)
- 1次f(2) + f(1) => f(3)
- 當我們計算f(4)的時候,根據遞歸過程,實際計算了
- 2次f(1) + f(0) => f(2)
- 1次f(2) + f(1) => f(3)
- 1次f(3) + f(2) => f(4)
- 當我們計算f(5)的時候,根據遞歸過程,實際計算了
- 3次f(1) + f(0) => f(2)
- 2次f(2) + f(1) => f(3)
- 1次f(3) + f(2) => f(4)
- 1次f(4) + f(3) => f(5)
- ...
通過這個規律,我們發現
- 計算f(4)的時候,f(2)的值被重復計算了2次
- 計算f(5)的時候,f(2)的值被重復計算了3次,f(3)的值被重復計算了2次
- ...
計算的數字越大,重復的計算就會越多。
怎么解決重復計算的問題?
通過上面的過程推導,我們可以這樣思考:
既然是計算過的數字值被重復計算,那么我們可以使用緩存的方式,把計算過的結果保存起來,更大的數字計算直接從緩存中取,不就可以省去計算過程了嗎?
因此,我們可以設定一個緩存對象:
var cache = {};
用來保存我們已經計算過的值,cache的使用過程將會是這樣的:
假設我們已經計算過了0~9的結果
cache = {
0: 0,
1: 1,
2: 1,
3: 2,
4: 3,
5: 5,
6: 8,
7: 13,
8: 21,
9: 34
};
當我們要計算fabonacci(5)的時候,我們就能夠1次從緩存中取出結果:
return cache[5];
是不是十分完美?
這樣就只計算了1次cache[5]!
那么,當我們計算fabonacci(10)的時候,我們只需要:
var result = cache[10] = cache[8] + cache[9];
return result;
只計算了1次cache[8] + cache[9],同時把結果保存進了緩存cache!
新版本的,性能高效的fabonacci程序
經過我們的努力,實現了如下性能高效的fabonacci程序:
var cache = {
0: 0,
1: 1
};
function fabonacci(n) {
if (typeof cache[n] === 'number') {
return cache[n];
}
var result = cache[n] = fabonacci(n - 1) + fabonacci(n - 2);
return result;
}
這個代碼,我們還可以再寫的優雅些,像下面這樣:
var cache = {
0: 0,
1: 1
};
function fabonacci(n) {
return typeof cache[n] === 'number'
? cache[n]
: cache[n] = fabonacci(n - 1) + fabonacci(n - 2);
}
OK!
現在,用我們上面的測試代碼測試一下新版本的fabonacci程序吧:
var start = new Date();
var result = fabonacci(50);
var end = new Date();
console.log('fabonacci(%d) = %d, use time %dms.',
50,
result,
end.getTime() - start.getTime());
// fabonacci(50) = 12586269025, use time 2ms.
!!!Perfect!!!這次計算fabonacci(50)只用了2ms的時間!
你喜歡函數式嗎?
上面的是C語言的風格,cache放在外部,你喜歡函數式編程嗎?
我們也可以編寫函數式風格的fabonacci程序,有助于減少變量混亂:
function fabonacci() {
var cache = {
0: 0,
1: 1
};
return function __fabonacci(n) {
return typeof cache[n] === 'number'
? cache[n]
: cache[n] = __fabonacci(n - 1) + __fabonacci(n - 2);
};
}
var fb = fabonacci();
fb(50);
另外,你會發現cache的鍵都是數字,而且是從0開始遞增計數,
所以,cache也可以用數組代替:
function fabonacci() {
var cache = [0, 1];
return function __fabonacci(n) {
return typeof cache[n] === 'number'
? cache[n]
: cache[n] = __fabonacci(n - 1) + __fabonacci(n - 2);
};
}
使用純C風格的代碼好,還是函數式風格的代碼好?
我覺得這兩個都很好,很直觀,容易維護,容易理解。
在Node.js的模塊下,你可以很安全的放置你的變量,所以C風格也不是問題。
這完全取決于你的風格!!!
當然,我們也可以奉上面向對象的風格:
function Fabonacci() {
if (!(this instanceof Fabonacci)) {
return new Fabonacci();
}
this._cache = [0, 1];
}
Fabonacci.prototype.compute = function (n) {
return typeof this._cache[n] === 'number'
? this._cache[n]
: this._cache[n] = this.compute(n - 1) + this.compute(n - 2);
};
Fabonacci().compute(50);