斐波那契數列的三種C++實現及時間復雜度分析


本文介紹了斐波那契數列的三種C++實現並詳細地分析了時間復雜度。

斐波那契數列定義:F(1)=1, F(2)=1, F(n)=F(n-1) + F(n-2) (n>2)

如何計算斐波那契數 F(n) 及時間復雜度 T(n) 呢?

我參考了一些資料總結了以下3種方法:遞歸法、順序法和矩陣乘法,並給出了基於C++的簡單代碼實現和時間復雜度分析。

如有不當,歡迎指正。

方法1:遞歸法

實現:

#include <stdio.h>
#include <iostream>

using namespace std;

long long Fibonacci1(int n)
{
    if (n < 1)
    {
        return 0;
    }
    if (n == 1 || n == 2)
    {
        return 1;
    }
    return Fibonacci1(n - 1) + Fibonacci1(n - 2);
}
int main()
{
    char cont = 'y';
    int n = 0;
    long long f = 0;
    cout << "Enter an integer:" << endl;
    while (cin >> n)
    {
        f = Fibonacci1(n);
        cout << "Fibonacci1(" << n << ") = " << f << endl;
        cout << "Continue(y or n)?" << endl;
        cin >> cont;
        if (cont == 'y' || cont == 'Y')
        {
            cout << "Enter an integer:" << endl;
        }
        else
            break;
    }
    return 0;
}
View Code

時間復雜度:

  設計算F(n)時調用遞歸的次數為call(n),T(n) = O(call(n))。

  1. 數學歸納法(沒興趣的可以直接看下面的方法2)

  當n = 1, 2時,F(n) = 1,call(n) = 1,T(n) = O(1)

  當n > 2時,F(n) = F(n-1) + F(n-2),call(n) = call(n-1) + call(n-2) + 1(執行加法運算)。

  n = 3時,call(3) = call(2) + call(1) + 1 = 3

  n = 4時,call(4) = call(3) + call(2) + 1 = 5

  n = 5時,call(5) = call(4) +call(3) + 1 = 9

  ……

  注意到:F(3) = 2,call(3) = 3

      F(4) = 3,call(4) = 5

      F(5) = 5,call(5) = 9

  由此猜測call(n) = 2 * F(n) - 1,下面用數學歸納法證明:

  當n = 3時,等式成立。

  假設n = k(k ≥ 3) 等式成立,即有:

  call(k) = 2 * F(k) - 1

  當n = k + 1時,

  F(k+1) = F(k) + F(k-1)

  call(k+1) = call(k) + call(k-1) + 1 = 2 * F(k) - 1 + 2 * F(k-1) -1 + 1 = 2 * F(k+1) - 1

  所以,當n = k + 1時,等式也成立。

  綜上,call(n) = 2 * F(n) - 1 對 n ≥ 2都成立

  所以,計算F(n)的時間復雜度T(n) = O(2 * F(n) - 1) = O(F(n)) ,

          

T(n)近似為O(2n)

  F(n)的計算可以參考“斐波那契數列”的百度百科:https://baike.baidu.com/item/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97

  2. 二叉樹法

  觀察以下二叉樹:

                f(5)

                 /    \

              f(4)   f(3)

             /  \   /  \

              f(3)  f(2) f(2)   f(1)

           /   \

          f(2)  f(1)

  call(n)可以轉化為求二叉樹所有節點的個數。n = 5時,二叉樹有4層,第一層有1個,第二層2個,第三層4個,第四層有2個。應用等比數列求和有call(n) = (1-2n-2)/(1-2) + 2 = 2n-2 + 1。T(n) = O(call(n)) = O(2n-2 + 1) = O(1/4 * 2n + 1) = O(2n)

方法2:順序法(從左到右依次求)

實現:

#include <stdio.h>
#include <iostream>

using namespace std;

long long Fibonacci2(int n)
{
    long long temp1 = 1;
    long long temp2 = 1;
    long long result = 0;

    if (n < 1)
    {
        return 0;
    }
    if (n == 1 || n == 2)
    {
        return 1;
    }
    for (int i = 3; i <= n; i++)
    {
        result = temp1 + temp2;
        temp1 = temp2;
        temp2 = result;
    }
    return result;
}

int main()
{
    char cont = 'y';
    int n = 0;
    long long f = 0;
    cout << "Enter an integer:" << endl;
    while (cin >> n)
    {
        f = Fibonacci2(n);
        cout << "Fibonacci2(" << n << ") = " << f << endl;
        cout << "Continue(y or n)?" << endl;
        cin >> cont;
        if (cont == 'y' || cont == 'Y')
        {
            cout << "Enter an integer:" << endl;
        }
        else
            break;
    }
    return 0;
}
View Code

時間復雜度:

   通過順序計算求得第n項,時間復雜度T(n) = O(n)

方法3:矩陣乘法

實現:

  F(n) = F(n-1) + F(n-2)是一個二階遞推數列,可以用矩陣乘法的形式表示為:

  [F(n), F(n-1)] = [F(n-1), F(n-2)] * [a, b; c, d]

  帶入n = 3, 4 可求出a = b = c = 1, d = 0。

  遞推可進一步得到[F(n), F(n-1)] = [F(2), F(1)] * [a, b; c, d]n-2 = [1, 1] * [1, 1; 1, 0]n-2,F(n)便迎刃而解。

  簡單介紹一下矩陣冪運算的實現過程。

   矩陣冪運算:

  以矩陣A的106次方為例,(106)10  = (1101010)2 = 21 + 23 + 25 + 26 = 2 + 8 + 32 + 64,所以,

      A106 = A2 * A8 * A32 * A64

  計算過程:

  1. result = E (單位矩陣),A平方得A2     0
  2. result *= A2,A2平方得A4               1
  3. A4平方得A8                                       0
  4. result *= A8,A8平方得A16             1
  5. A16平方得A32                                    0
  6. result *= A32,A32平方得A64          1
  7. result *= A64,A64平方得A128        1

  即106的二進制形式有多少位,就要調用平方運算幾次,這個次數p其實滿足:2p-1 ≤ n < 2p,即p ≈ log2(n),這樣就將方法2中復雜度為O(n)的計算降到了O(log2(n))。

#include <stdio.h>
#include <iostream>

using namespace std;

enum Status { kValid = 0, kInvalid };
int g_nStatus = kValid;

class Matrix
{
public:
    Matrix(int rows, int cols);    // 構建一個全零矩陣
    int GetRows() const;        // 返回矩陣行數
    int GetCols() const;        // 返回矩陣列數
    long long **p;                // 指針數組
private:
    int rows_;                    // 矩陣行數
    int cols_;                    // 矩陣列數
};

Matrix::Matrix(int rows, int cols) : rows_(rows), cols_(cols)
{
    if (rows < 0 || cols_ < 0)
    {
        cout << "Matrix error!\n";
        g_nStatus = kInvalid;
        return;
    }
    // 分配空間
    p = new long long *[rows_];
    for (int i = 0; i < rows_; i++)
    {
        p[i] = new long long[cols_];
    }
    // 初始化
    for (int i = 0; i < rows_; i++)
    {
        for (int j = 0; j < cols; j++)
        {
            p[i][j] = 0;
        }
    }
}
int Matrix::GetRows() const
{
    return rows_;
}
int Matrix::GetCols() const
{
    return cols_;
}

// 矩陣乘法運算
Matrix MatrixMultiply(Matrix& m1, Matrix& m2)
{
    Matrix result(m1.GetRows(), m2.GetCols());
    if (m1.GetCols() == m2.GetRows())
    {
        for (int i = 0; i < result.GetRows(); i++)
        {
            for (int j = 0; j < result.GetCols(); j++)
            {
                for (int k = 0; k < m1.GetCols(); k++)
                {
                    result.p[i][j] += m1.p[i][k] * m2.p[k][j];
                }
            }
        }
        g_nStatus = kValid;
    }
    return result;
}

// 矩陣冪運算
Matrix MatrixPowder(Matrix& m, int p)
{
    g_nStatus = kInvalid;
    Matrix result(m.GetRows(), m.GetCols());
    if (m.GetRows() == m.GetCols())
    {
        for (int i = 0; i < result.GetRows(); i++)
        {
            result.p[i][i] = 1;
        }
        for (; p != 0; p >>= 1)
        {
            if ((p & 1) != 0)
            {
                result = MatrixMultiply(result, m);
            }
            m = MatrixMultiply(m, m);
        }
    }
    return result;
}

long long Fibonacci3(int n)
{
    if (n < 1)
    {
        return 0;
    }
    if (n == 1 || n == 2)
    {
        return 1;
    }
    Matrix m1(2, 2);
    m1.p[0][0] = 1;
    m1.p[0][1] = 1;
    m1.p[1][0] = 1;
    m1.p[1][1] = 0;

    Matrix result = MatrixPowder(m1, n - 2);
    if (g_nStatus == kInvalid)
    {
        cout << "Matrix error!\n";
        return 0;
    }
    return (result.p[0][0] + result.p[1][0]);
}

int main()
{
    char cont = 'y';
    int n = 0;
    long long f = 0;
    cout << "Enter an integer:" << endl;
    while (cin >> n)
    {
        f = Fibonacci3(n);
        cout << "Fibonacci(" << n << ") = " << f << endl;
        cout << "Continue(y or n)?" << endl;
        cin >> cont;
        if (cont == 'y' || cont == 'Y')
        {
            cout << "Enter an integer:" << endl;
        }
        else
            break;
    }
    return 0;
}
View Code

時間復雜度:

   時間復雜度等於求矩陣n次方的復雜度,即O(log2n)

總結: 

三種方法的運行時間比較

可以明顯感受到方法1的呈指數增長的時間復雜度。

方法1太耗時,下面再比較方法2和方法3:

方法3秒殺方法2!

感悟

  寫完這篇隨筆,深深體會到了算法的神奇。完成基本需求也許不難,又快又好的完成就需要有算法的功底啦。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM