《程式設計之美》一書中講述斐波那契數列的問題,之前大學本科的時候就接觸這個問題,那時候開始就知道使用遞迴來計算,可是一直沒有考慮過改進下該演算法。。。囧~~菜 直到看到這本書,發現原來之前好多問題都可以優化,斐波那契就是其中之一,其中書本中講述了三種方法、:
- 第一種就是對平時的遞迴演算法進行優化,增加了陣列專門記錄每個子問題的解,實際上是動態規劃的思想;
- 第二種利用數學中通項公式,利用F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*)遞推公式得到F(n)的特徵方程,然後直接利用公式計算。。。好bug哇~ 不過存在無理數,資料的精度就無法保證了~~
- 利用分治策略,將問題轉換為矩陣乘冪的問題。
其餘兩種大家可以去網上找答案腦補下,我這裡關鍵闡述下第三種哈~
基本的原理是運用矩陣:
(Fn Fn-1) = (Fn-1 Fn-2)*A,求解得到
A = { {1,1}, {1,0}}
然後通過遞推式
(Fn Fn-1) = (Fn-1 Fn-2)*A = (Fn-2 Fn-3)*A^2 = .... = (F1 F0)*A^(n-1)
然後只要計算A^(n-1),再與矩陣(F1 F0)相乘,就可以的得到 Fn的值,我非常佩服作者的是,能把重複計算轉化為對A的n-1乘法計算,因為計算A的n-1乘法有快速相乘的方法,比如計算m的10000次方,其實最少的計算次數,等於10000的最高位元位的位置與零的個數,即14~28次乘法運算,並不需要10000次乘法。
對於n有 n = ak*2^k + ak-1*2^k-1 + ... + a1*2 + a0,其中ai = 0 或1 ,i = 0,1,2... k ,將冪數變為二進位制形式表示,此時我們只需要進行logn次的運算(以2為底)。 也即 快速指數相乘法。
下面是自己實現的程式碼,可能寫的比較挫~ 關鍵是matrix矩陣的寫法哈~
#include <iostream> using namespace std; // struct Matrix { Matrix(int n) { if (n >= 1) { nLength = n; data = new long long*[nLength]; for (int i = 0; i!= n; i++) { data[i] = new long long[nLength]; } } else { data = NULL; } } Matrix() { data = NULL; } // 拷貝建構函式 Matrix(const Matrix &m) { int nLen = m.GetMatrixLength(); if (nLen >= 1) { nLength = nLen; data = new long long*[nLength]; for (int i = 0; i!= nLen; i++) { data[i] = new long long[nLength]; } // for (int j = 0; j!=nLen; j++) { for (int k = 0; k != nLen; k++) { data[j][k] = m.GetMatrixValue(j,k); } } } else { data = NULL; } } // 過載賦值操作符 // 錯誤都在這裡。。。注意因為涉及到指標,一定要深拷貝 Matrix& operator = (const Matrix& m) { // 如果是自己 直接返回即可 if (this == &m) { return *this; } // 如果當前物件已有空間也需要釋放掉 重新分配 if (data) { for (int i = 0; i!=nLength ; i++) { if (data[i]) { delete[] data[i]; } } delete[] data; data = NULL; } // if (data == NULL) { nLength = m.GetMatrixLength(); data = new long long*[nLength]; for (int i = 0; i!= nLength; i++) { data[i] = new long long[nLength]; } } for (int i=0; i!= nLength; i++) { for (int j = 0; j != nLength; j++) { this->data[i][j] = m.GetMatrixValue(i,j); } } // 方法返回時 呼叫析構 函式 釋放空間 return *this; } // 釋放所有分配的空間 ~Matrix() { if (data) { for (int i = 0; i!=nLength ; i++) { if (data[i]) { delete[] data[i]; } } delete[] data; data = NULL; } } void setIdentity() { for (int i = 0; i != nLength; i++) { for (int j = 0; j!= nLength; j++) { data[i][j] =( (i ==j) ? 1 : 0 ); } } } // 過載乘等操作符 一定要記得深拷貝哦 親 Matrix& operator *= (const Matrix& m) { if (this->nLength == m.nLength) { Matrix tmp(m.nLength); for (int i = 0; i!= nLength; i++) { for (int j = 0; j!= nLength; j++) { // 計算每個位置的值 long long nValue = 0; for (int p = 0; p != nLength; p++) { nValue += data[i][p] * (m.GetMatrixValue(p,j)); } tmp.SetMatrixValue(i,j,nValue); } } // *this = tmp; // 返回時會呼叫析構 釋放tmp物件 } return *this; } // void SetMatrixValue(const int i,const int j, const long long nValue) { data[i][j] = nValue; } long long GetMatrixValue(const int i, const int j) const { long long n = data[i][j]; return n; } unsigned int GetMatrixLength() const { return nLength; } private: unsigned int nLength; long long **data; }; void GetMatrixPow(const Matrix &m, int index,Matrix &result) { // 先單位化結果矩陣 result.setIdentity(); // Matrix tmp(m.GetMatrixLength()); // // tmp = m; Matrix tmp = m; // C語言風格的賦值 會呼叫拷貝建構函式 for (;index;index >>=1) { if (index & 1) { result *= tmp; } tmp *= tmp; } // 該函式結束 呼叫matrix解構函式 釋放tmp所佔空間 } long long Fibonacci(int n) { if (n == 0) { return 0; } else if (n == 1) { return 1; } long long nValue = 0; Matrix A(2); A.SetMatrixValue(0,0,1); A.SetMatrixValue(0,1,1); A.SetMatrixValue(1,0,1); A.SetMatrixValue(1,1,0); // 結果矩陣一定還是2*2的 Matrix result(2); GetMatrixPow(A , n-1,result); nValue = result.GetMatrixValue(1,0);//F0* result.GetMatrixValue(0,0) + F1* result.GetMatrixValue(1,0); return nValue; } int main() { int N; cout<<"輸入你要計算的斐波那契數列的項(目前可以計算到93~):"; cin>>N; for (int i = 0; i <= N; i++) { long long nResult = Fibonacci(i); cout<<nResult<<endl; } return 0; }
程式的執行結果如下所示:
當然這裡矩陣每個元素我用了long long 64位,經測試可以計算到93~ 需要改進的話,需要選擇合適的變數型別。