矩阵加速计算斐波那契数列

摘要

    通过矩阵加速计算,将计算斐波那契数列的时间复杂度优化到O\left ( logn \right ),空间复杂度O\left ( 1 \right )

 

目录

一、斐波那契数列

二、传统计算方法

    1.递归法

    2.记忆化递归

三、矩阵加速计算斐波那契数列

    1.斐波那契数矩阵

    2.C++代码实现矩阵加速斐波那契数列

    3.快速幂

    4.全部代码

 

一、斐波那契数列

    斐波那契数列,又称黄金分割数列,是指这样一个数列:0,1,1,2,3,5,8,13,……

     在数学上通常以如下递归方法定义:F(0)=0, F(1)=1,F(n)=F(n-1)+F(n-2)(n\geq 2, n\in N+)

二、传统计算方法

1.递归法

   即使用最普通的递归算法进行计算,代码如下:

int Fib(int n){
    if(n == 0) return 0;
    if(n == 1) return 1;
    return Fib(n-1)+Fib(n-2);
}

    该算法时间复杂度为O\left ( \left (\frac{2}{\sqrt{5} -1} \right ) ^{n}\right ),时间复杂度推导可参考该篇博客点击跳转。可见当n较大时,时间复杂度将会非常大,满足不了时间要求。

2.记忆化递归(动态规划)

    本质还是用递归的方法。但朴素递归存在重复计算的缺陷。如图是计算斐波那契数列的递归树。

    可见,在递归过程中,很多量被重复计算过很多次。如果我们在计算的同时,将计算过的量存储起来,即加入“记忆化”,可以使计算变得相对高效。

int f[MaxN] = {0};

int Fib(int n){
    if(n == 0) return 0;
    if(n == 1) return 1;
    if(f[n]!=0) return f[n];
    return f[n] = Fib(n-1)+Fib(n-2);
}

   由此算法时间复杂度可以优化到O\left ( n \right ),但显然这种做法是以空间换时间,需要耗费相对较大的空间复杂度O\left ( n \right )。虽然时间复杂度已经优化到线性,但面对较大的数据仍然略显吃力。

三、矩阵加速计算斐波那契数列

1.构造斐波那契矩阵

    笔者假定读者已经熟悉矩阵乘法的计算规则,如果不是,请参考此处

    我们首先构造这样一个2x2的矩阵:

    \begin{bmatrix} F\left ( n+1 \right ) & F\left ( n \right )\\ F\left ( n \right )& F\left ( n-1 \right ) \end{bmatrix}

    再构造这样一个2x2的矩阵:

    \begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}

   如果我们把如上两个矩阵会得到什么效果?

    \begin{bmatrix} F\left ( n+1 \right ) & F\left ( n \right )\\ F\left ( n \right )& F\left ( n-1 \right ) \end{bmatrix} \times \begin{bmatrix} 1 &1 \\ 1&0 \end{bmatrix} = \begin{bmatrix} F\left ( n+1 \right ) +F\left ( n \right )&F\left ( n+1 \right ) \\ F\left ( n \right )+F\left ( n-1 \right )& F\left ( n \right ) \end{bmatrix} = \begin{bmatrix} F\left ( n+2 \right ) & F\left ( n+1 \right )\\ F\left ( n+1 \right ) & F\left ( n \right ) \end{bmatrix}

    由此我们可以得到一个递推公式,如果令:

    T\left ( n \right ) = \begin{bmatrix} F\left ( n+1 \right ) &F\left ( n \right ) \\ F\left ( n \right )& F\left ( n-1 \right ) \end{bmatrix}

    则:

    T\left ( n \right ) = T\left ( n-1 \right ) \times \begin{bmatrix} 1 &1 \\ 1 &0 \end{bmatrix} = T\left ( n-2 \right ) \times \begin{bmatrix} 1 &1 \\ 1&0 \end{bmatrix}^{2} =...... = \begin{bmatrix} 1&1 \\ 1& 0 \end{bmatrix}^{n}

    由此就出现了非常神奇的效果:

    \begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}^{1}= \begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}        F\left ( 1 \right )=1

    \begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}^{2}= \begin{bmatrix} 2&1 \\ 1&1 \end{bmatrix}        F\left ( 2 \right )=1

    \begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}^{3}= \begin{bmatrix} 3&2 \\ 2&1 \end{bmatrix}        F\left ( 3 \right )=2

    \begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}^{4}= \begin{bmatrix} 5&3 \\ 3&2 \end{bmatrix}        F\left ( 4 \right )=3

    \begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}^{5}= \begin{bmatrix} 8&5 \\ 5&3 \end{bmatrix}        F\left ( 5 \right )=5

    \begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}^{6}= \begin{bmatrix} 13&8 \\ 8&5 \end{bmatrix}       F\left ( 6 \right )=8

    ……

    \begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}^{n}= \begin{bmatrix} F\left ( n+1 \right )&F\left ( n \right ) \\ F\left ( n \right )&F\left ( n-1 \right ) \end{bmatrix}        F\left ( n \right )=F\left ( n \right )

    因此,如果我们要计算F\left ( n \right ),我们只需要计算\begin{bmatrix} 1 & 1\\ 1&0 \end{bmatrix}^{n},然后取左下角(或右上角)的元素即可。

    笔者将\begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}称为单位斐波那契矩阵。

2.C++代码实现矩阵计算斐波那契数列

    用结构体构造矩阵。

struct Matrix{
    int data[2][2];
}

    构造一个成员初始化函数,方便对该数据类型进行初始化

struct Matrix{
    int data[2][2];
    Matrix(int data00, int data01, int data10, int data11){
        data[0][0] = data00; data[0][1] = data01;
        data[1][0] = data10; data[1][1] = data11;
    }
};

    该成员函数构造好后,就可以用如下方法初始化Matrix类型变量了。

Matrix Fib = Matrix(1, 1, 1, 0);

    对乘法运算符*重载,方便矩阵乘法运算。

struct Matrix{
    static const int MOD = 10007;
    int data[2][2];
    Matrix(int data00, int data01, int data10, int data11){
        data[0][0] = data00; data[0][1] = data01;
        data[1][0] = data10; data[1][1] = data11;
    }
    Matrix operator * (const Matrix &a) const{
        Matrix ans = Matrix(0, 0, 0, 0);
        for(int i = 0; i<2; ++i)
            for(int j = 0; j<2; ++j)
                for(int k = 0; k<2; ++k) 
                    ans.data[i][j] = (ans.data[i][j] + data[i][k]*a.data[k][j])%MOD;
        return ans;
    }
};

    这样我们就可以像这样直接调用*运算符进行矩阵乘法运算了。

Matrix Fib = Matrix(1, 1, 1, 0);
Fib = Fib * Fib;
printf("%d\n", Fib.data[1][0]);  //F(2)

   构造成员函数进行幂运算。   

struct Matrix{
    static const int MOD = 10007;
    int data[2][2];
    Matrix(int data00, int data01, int data10, int data11){
        data[0][0] = data00; data[0][1] = data01;
        data[1][0] = data10; data[1][1] = data11;
    }
    Matrix operator * (const Matrix &a) const{
        Matrix ans = Matrix(0, 0, 0, 0);
        for(int i = 0; i<2; ++i)
            for(int j = 0; j<2; ++j)
                for(int k = 0; k<2; ++k) 
                    ans.data[i][j] = (ans.data[i][j] + data[i][k]*a.data[k][j])%MOD;
        return ans;
    }
    Matrix pow(int power){
        Matrix ans = (*this);
        for(int i = 2; i<=power; ++i)
            ans = ans * (*this);
        return ans;
    }
};

   由此我们便能实现斐波那契数列的计算。

#include <cstdio>
using namespace std;

struct Matrix{
    static const int MOD = 10007;
    int data[2][2];
    Matrix(int data00, int data01, int data10, int data11){
        data[0][0] = data00; data[0][1] = data01;
        data[1][0] = data10; data[1][1] = data11;
    }
    Matrix operator * (const Matrix &a) const{
        Matrix ans = Matrix(0, 0, 0, 0);
        for(int i = 0; i<2; ++i)
            for(int j = 0; j<2; ++j)
                for(int k = 0; k<2; ++k) 
                    ans.data[i][j] = (ans.data[i][j] + data[i][k]*a.data[k][j])%MOD;
        return ans;
    }
    Matrix pow(int power){
        Matrix ans = (*this);
        for(int i = 2; i<=power; ++i)
            ans = ans * (*this);
        return ans;
    }
};

int main(){
    int n;
    scanf("%d", &n);
    Matrix ans = Matrix(1, 1, 1, 0);
    printf("%d\n", ans.pow(n).data[1][0]);
    return 0;
}

    算法时间复杂度为O\left ( n \right ),空间复杂度优化到O\left ( 1 \right )。时间复杂度我们可以继续优化。

3.快速幂

    关于快速幂算法可以参考这篇文章

    现在我们将快速幂推广到矩阵乘法。

    由于n较大时运算结果可能非常大,在此我们对结果进行10007取余运算。

    矩阵乘法作为一个代数系统,不难证明\begin{bmatrix} 1 &0 \\ 0& 1 \end{bmatrix}是他的幺元。即\begin{bmatrix} a &b \\ c & d \end{bmatrix}^{0}} = \begin{bmatrix} 1& 0\\ 0&1 \end{bmatrix}

struct Matrix{
    static const int MOD = 10007;
    int data[2][2];
    Matrix(int data00, int data01, int data10, int data11){
        data[0][0] = data00; data[0][1] = data01;
        data[1][0] = data10; data[1][1] = data11;
    }
    Matrix operator * (const Matrix &a) const{
        Matrix ans = Matrix(0, 0, 0, 0);
        for(int i = 0; i<2; ++i)
            for(int j = 0; j<2; ++j)
                for(int k = 0; k<2; ++k) 
                    ans.data[i][j] = (ans.data[i][j] + data[i][k]*a.data[k][j])%MOD;
        return ans;
    }
    Matrix pow( int power){
        Matrix ans = Matrix(1, 0, 0, 1);
        Matrix base = (*this);
        while(power){
            if(power&1) ans = ans * base;
            base = base * base;
            power >>= 1;
        }
        return ans;
    }
};

    由此我们将时间复杂度优化到O\left ( logn \right ),空间复杂度为O\left ( 1 \right ),可以说是一个非常优的算法了。

4.矩阵加速计算斐波那契数列全部代码

#include <cstdio>
using namespace std;

struct Matrix{
    static const int MOD = 10007;
    int data[2][2];
    Matrix(int data00, int data01, int data10, int data11){
        data[0][0] = data00; data[0][1] = data01;
        data[1][0] = data10; data[1][1] = data11;
    }
    Matrix operator * (const Matrix &a) const{
        Matrix ans = Matrix(0, 0, 0, 0);
        for(int i = 0; i<2; ++i)
            for(int j = 0; j<2; ++j)
                for(int k = 0; k<2; ++k) 
                    ans.data[i][j] = (ans.data[i][j] + data[i][k]*a.data[k][j])%MOD;
        return ans;
    }
    Matrix pow( int power){
        Matrix ans = Matrix(1, 0, 0, 1);
        Matrix base = (*this);
        while(power){
            if(power&1) ans = ans * base;
            base = base * base;
            power >>= 1;
        }
        return ans;
    }
};

int main(){
    int n;
    scanf("%d", &n);
    Matrix ans = Matrix(1, 1, 1, 0);
    printf("%d\n", ans.pow(n).data[1][0]);
    return 0;
}

 

### 使用矩阵乘法计算斐波那契数列的时间复杂度 对于传统的递归方法,时间复杂度为指数级 \(T(n) = T(n-1) + T(n-2)\),这是因为存在大量的重复计算[^1]。 然而,通过使用矩阵乘法可以显著提高效率。考虑以下矩阵方程: \[ \begin{bmatrix} F_{n} \\ F_{n-1} \end{bmatrix} = \begin{bmatrix} 1 & 1\\ 1 & 0 \end{bmatrix} \times \begin{bmatrix} F_{n-1}\\ F_{n-2} \end{bmatrix} \] 因此, \[ F_n = \left[\begin{array}{cc}1&1\\1&0\end{array}\right]^{\!n-1} \times \begin{bmatrix}F_1 \\ F_0\end{bmatrix}. \] 为了快速求解上述幂运算,可采用分治策略——即所谓的“快速幂”。这种方法能够将原本O(n)次的连乘操作减少到\( O(\log n) \)[^2]。 具体来说,在每次迭代过程中只做一次平方以及可能的一次额外相乘即可完成整个过程。由于单次矩阵乘法涉及固定数量的操作(这里指两个2×2矩阵),所以这部分开销是常量级别的。综上所述,利用矩阵乘法配合快速幂技术来获取第n个斐波那契数值的整体时间复杂度大约为\( O(\log n) \)[^2]。 ```cpp #include <iostream> using namespace std; void multiply(int F[2][2], int M[2][2]) { int x = F[0][0]*M[0][0] + F[0][1]*M[1][0]; int y = F[0][0]*M[0][1] + F[0][1]*M[1][1]; int z = F[1][0]*M[0][0] + F[1][1]*M[1][0]; int w = F[1][0]*M[0][1] + F[1][1]*M[1][1]; F[0][0] = x; F[0][1] = y; F[1][0] = z; F[1][1] = w; } void power(int F[2][2], int n) { if (n == 0 || n == 1) return; int M[2][2] = {{1, 1}, {1, 0}}; power(F, n / 2); multiply(F, F); if (n % 2 != 0) multiply(F, M); } int fib(int n) { int F[2][2] = {{1, 1}, {1, 0}}; if (n == 0) return 0; power(F, n - 1); return F[0][0]; } // 测试代码省略... ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值