斐波那契數列的分治法計算

菜鳥加貝的爬升發表於2013-10-07

           《程式設計之美》一書中講述斐波那契數列的問題,之前大學本科的時候就接觸這個問題,那時候開始就知道使用遞迴來計算,可是一直沒有考慮過改進下該演算法。。。囧~~菜   直到看到這本書,發現原來之前好多問題都可以優化,斐波那契就是其中之一,其中書本中講述了三種方法、:

  1. 第一種就是對平時的遞迴演算法進行優化,增加了陣列專門記錄每個子問題的解,實際上是動態規劃的思想;
  2. 第二種利用數學中通項公式,利用F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*)遞推公式得到F(n)的特徵方程,然後直接利用公式計算。。。好bug哇~ 不過存在無理數,資料的精度就無法保證了~~
  3. 利用分治策略,將問題轉換為矩陣乘冪的問題。

  其餘兩種大家可以去網上找答案腦補下,我這裡關鍵闡述下第三種哈~

基本的原理是運用矩陣:

(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;
}

程式的執行結果如下所示:

image

當然這裡矩陣每個元素我用了long long 64位,經測試可以計算到93~ 需要改進的話,需要選擇合適的變數型別。

相關文章