斐波那契數列一般都用於介紹遞歸的思想。
我們知道斐波那契數列的通項公式(n>1)如下:
F(n) = F(n-1) + F(n-2)
按照這個公式寫個代碼就很容易了:
int fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
}
return fibonacci(n-1) + fibonacci(n-2);
}
這種代碼簡單又優雅,但是缺點也很明顯,就是慢:
又慢又占空間。
這是為什么呢?
我們來看看遞歸都做了什么,以n=5為例,如下圖:
這里會有很多的重復計算過程,這個重復計算的過程叫做遞歸棧,遞歸最大的毛病就是遞歸棧里重復的東西太多。
觀察上圖,如果從下向上,效果會更加的好,可以借助數組來實現,我們知道斐波那契數列其實就是這樣的一個數列:
0,1,1,2,3,5,8...
只要將第一和第二項算出來保存在數組中,后面的項就很好計算了:
public int Fibonacci2(int n) {
int[] fib = new int[45];
fib[0] = 0;
fib[1] = 1;
for (int i = 2; i <= n; i++) {
fib[i] = fib[i - 1] + fib[i - 2];
}
return fib[n];
}
n=5的時候,這個數組最終是這樣的[0,1,1,2,3,5,......],此時的時間復雜度僅僅是O(n),比遞歸的O(2^n)好太多了:
僅僅消耗了17ms,是原先的5.8%左右。
但是,繼續觀察上面的數組:
[0, 1, 1, 2, 3, 5],我們會發現這個數組中,f(0)和f(2)並沒有用到,這兩個其實可以精簡掉,於是代碼變成了這樣:
public int Fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
}
int a = 0;
int b = 1;
int c = 0;
for (int i = 2; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return c;
}
n=5的情況下,這個循環只會計算f(1),f(3),f(4)和f(5)的值,並不用數組去保存,因此空間也節省了下來。
另外我試了試C和Java語言用第三種解法的速度差異,發現C語言還是很有優勢的:
同樣的算法竟然只用了5ms。