斐波那契數列的三種實現方式(遞迴、迴圈、矩陣)
《劍指offer》裡講到了一種斐波那契數列的 O(logN) 時間複雜度的實現,覺得挺有意思的,三種方法都記錄一下。
一、遞迴
一般來說遞迴實現的程式碼都要比迴圈要簡潔,但是效率不高,比如遞迴計算斐波那契數列第n個元素。
long long Fibonacci_Solution1(unsigned int n) { // printf("%d ", n); if (n <= 0) return 0; if (n == 1) return 1; return Fibonacci_Solution1(n - 1) + Fibonacci_Solution1(n - 2); }
如果計算數列的第4個位置上(從0開始)的數(0 1 1 2 3),也就是3,上邊的 printf 輸出應該是 4 3 2 1 0 1 2 1 0,這是因為計算 F(4) 要計算 F(3) 和 F(2),而計算 F(3) 的時候又要計算 F(2) 和 F(1),所以會有很多重複計算。用下圖可以更好地說明。
遞迴雖然有簡潔的優點,但它同時也有顯著地缺點。遞迴由於是函式呼叫自身,而函式呼叫是有空間和時間的消耗的:每一次函式呼叫,都需要在記憶體棧中分配空間以儲存引數、返回地址及臨時變數,而且往棧裡壓入資料和彈出資料都需要時間。
而且除了效率問題之外,遞迴可能引起 呼叫棧溢位
二、迴圈
最常規的正確做法就是用迴圈從小到大計算。
long long Fibonacci_Solution2(unsigned n) { if (n <= 0) return 0; if (n == 1) return 1; long long fib1 = 1, fib0 = 0, fibN = 0; for (unsigned int i = 2; i <= n; ++i) { fibN = fib1 + fib0; fib0 = fib1; fib1 = fibN; } return fibN; }
或者下邊這種
long long Fibonacci_Solution2(unsigned n)
{
if (n <= 0) return 0;
if (n == 1) return 1;
long long a = 0, b = 1;
for (unsigned int i = 2; i <= n; ++i)
{
b = a + b;
a = b - a;
}
return b;
}
三、矩陣
數中提到了一種 O(logN) 時間複雜度的演算法,就是利用數學公式計算。
首先需要知道下邊這個數學公式:
這個公式用數學歸納法可以證明,所以只需要計算右邊矩陣的 n-1 次方就能得到 f(n),現在問題就變成了計算 2x2 矩陣的 n-1 次方,這樣做 n-2 次乘法就可以了,時間複雜度還是 O(N),但是還可以加速,如下式:
所以我們可以看出,想求 n 次方可以求出 n / 2 次方再平方,所以時間複雜度可以將為 O(logN)。
struct Matrix2By2
{
Matrix2By2(long long m00 = 0, long long m01 = 0, long long m10 = 0, long long m11 = 0)
:m_00(m00), m_01(m01), m_10(m10), m_11(m11) {}
long long m_00, m_01, m_10, m_11;
};
Matrix2By2 MatrixMultiply(const Matrix2By2& matrix1, const Matrix2By2& matrix2)
{
return Matrix2By2( matrix1.m_00 * matrix2.m_00 + matrix1.m_01 * matrix2.m_10,
matrix1.m_00 * matrix2.m_01 + matrix1.m_01 * matrix2.m_11,
matrix1.m_10 * matrix2.m_00 + matrix1.m_11 * matrix2.m_10,
matrix1.m_10 * matrix2.m_01 + matrix1.m_11 * matrix2.m_11 );
}
Matrix2By2 MatrixPower(unsigned int n)
{
assert(n > 0);
Matrix2By2 matrix;
if (n == 1)
matrix = Matrix2By2(1, 1, 1, 0);
else if (n % 2 == 0) // n是偶數
{
matrix = MatrixPower(n / 2);
matrix = MatrixMultiply(matrix, matrix);
}
else if (n % 2 == 1) // n是奇數
{
matrix = MatrixPower((n - 1) / 2);
matrix = MatrixMultiply(matrix, matrix);
matrix = MatrixMultiply(matrix, Matrix2By2(1, 1, 1, 0));
}
return matrix;
}
long long Fibonacci_Solution3(unsigned int n)
{
if (n <= 0) return 0;
if (n == 1) return 1;
Matrix2By2 PowerNMinus2 = MatrixPower(n - 1);
return PowerNMinus2.m_00;
}
為了測試上邊三種方式的程式碼的正確性,可以用如下樣例來測試。
// ====================測試程式碼====================
void Test(int n, int expected)
{
if (Fibonacci_Solution1(n) == expected)
printf("Test for %d in solution1 passed.\n", n);
else
printf("Test for %d in solution1 failed.\n", n);
if (Fibonacci_Solution2(n) == expected)
printf("Test for %d in solution2 passed.\n", n);
else
printf("Test for %d in solution2 failed.\n", n);
if (Fibonacci_Solution3(n) == expected)
printf("Test for %d in solution3 passed.\n", n);
else
printf("Test for %d in solution3 failed.\n", n);
}
int main(int argc, char* argv[])
{
Test(0, 0);
Test(1, 1);
Test(2, 1);
Test(3, 2);
Test(4, 3);
Test(5, 5);
Test(6, 8);
Test(7, 13);
Test(8, 21);
Test(9, 34);
Test(10, 55);
Test(40, 102334155);
return 0;
}