1. 程式人生 > >斐波那契數列的幾種計算機解法

斐波那契數列的幾種計算機解法

斐波那契數列傳說起源於一對非常會生的兔子。定義:


這個數列有很多奇妙的性質(比如 F(n+1)/F(n) 的極限是黃金分割率),用計算機有效地求解這個問題的解是一個比較有意思的問題,本文一共提供了4種解法。

解法一:遞迴

這是最最最直觀的想法,是每個人都能編寫的簡單程式,優點是非常明顯的:簡單易懂,清晰明瞭。但是缺點就是效率非常低,時間複雜度是指數級的。舉個例子,比如要計算F(5),那麼就要就算F(4)+F(3),而在計算F(4)的時候又要計算F(3),導致了 F(3)的重複計算,如果n越來越大,重複的計算量是無比巨大的,這就是瓶頸所在。

程式碼:

int F(int n)
{
	if(n <= 0)
		return 0;
	else if(n == 1)	
		return 1;
	else
		return F(n-1) + F(n-2);
}

那麼怎麼克服這個問題?這就引出瞭解法二。

解法二:動態規劃

解法一的缺點是因為重複計算,那麼我們只需要把一些已經計算過的答案存放起來,那這個缺點就解決了。我們用一維陣列來實現,比如 F(5)就存放在陣列下標為5的資料單元裡。

程式碼:

#include<iostream>
using namespace std;
int F(int n)
{
	if(n<=0)
		return 0;
	if(n==1)
		return 1;
	int* ans = new int[n+1];
	ans[0] = 0;
	ans[1] = 1;

	for(int i=2; i<=n; i++)
		ans[i] = ans[i-1] + ans[i-2];
	
	int tmp = ans[n];
	delete[] ans;
	return tmp;
}


這個演算法的時間複雜度是O(n),空間複雜度也是O(n)。複雜度來到了線性,這是我們所高興的,但是,是否還有比線性更好的複雜度?

解法三:求解通項公式

如果我們知道了通項公式,那麼我們就能在 O(1)的時間內得到F(n)。這是一個完美的時間複雜度。

這裡只介紹一種求解通項公式的技巧——矩陣。矩陣作為一個強大的數學工具有太多不為人知的應用。當然還有其它方法,比如高中數學競賽裡面的特徵方程,有興趣的讀者可以自行搜尋一下。

我們很容易發現:

所以剩下的問題就是隻要求出了就求出了F(n)。

求這個矩陣的 n次方的解法也有很多,這裡介紹一種方法——相似對角化。

於是

上述方程的解為

 

於是解得

 的基礎解係為

的基礎解係為

所以令

 

我們有:

所以,

 

兩邊取n次方,我們得到:

最後,做矩陣運算(實際上我們只需要 An 裡左下角的資料),便可以得到:

通項公式的計算就完成了。(推導過程需線性代數基礎)

時間複雜度是完美了,那麼有沒有缺點呢?當然有,公式裡引入了無理數,所以不能保證運算結果的精度。

解法四:分治

解法三的缺點是精度無法保證,那麼我們自然就想到,然計算機自己去計算,進行n-1次矩陣乘法不就行了。這是最直觀的想法,雖然是線性的,但複雜度還是不令人滿意,有沒有更好的複雜度?比如 log2 (n)?答案是有的。

先來看一個背景知識:一個十進位制正數 n的用二進位制表示要用floor( log2(n) )+1 位。(floor(x)返回不大於 x的最大整數)

用二進位制方式表示 n

所以

如果能得到的值就可以經過 log2 (n)次乘法得到

顯然可以通過遞推得到:

程式碼:

Class Matrix;	//假設已經實現了矩陣類

Matrix MatrixPow(const Matrix &m, int n)	//計算m的n次方
{
	Matrix result = Matrix::identity;	//單位矩陣
	Matrix tmp = m;
	for(; n; n >>= 1)
	{
		if(n & 1)
			result *= tmp;
		tmp *= tmp;
	}
}


int F(int n)
{
	Matrix an = MatrixPow(A, n);
	return F1*an(1,0) + F0*an(1,1);		//an(1,0)表示an的第1行第0列的元素
}

時間複雜度僅為O(log2 (n))

參考資料: [3]   線性代數教材