裴波那数列的计算

今天看到了一篇文章关于如何求解裴波那数列的方法。觉得叙述得很好。

斐波那契数列的定义如下:

f(1) = f(2) = 1;
f(n) = f(n-1) + f(n-2);

这个定义是递归的,因此很容易根据以上的定义写出它的递归解法, 由于这个数列的递增速度飞快XD,我们先重定义一下long long好方便使用:

typedef long long ll;

递归版本:

ll fib(ll n){
    if(n < 1) return -1;
    if(n == 1 || n == 2) return 1;
    else return fib(n-1) + fib(n-2);
}

当然了,根据定义我们也可以很容易地写出它的非递归版本(迭代版本):

ll fib1(ll n){
    if(n < 1) return -1;
    if(n == 1 || n == 2) return 1;
    ll a = 1, b = 1;
    for(ll i=3; i<=n; ++i){
        ll c = a + b;
        a = b;
        b = c;
    }
    return b;
}

空间复杂度O(1),时间复杂度O(n),看起来既简单又快速。可是,我们还有更快的解法。 根据上面的递推公式,我们可以得到它的矩阵版本:

这里写图片描述

从上图可以看出,写成矩阵递推形式,可以让我们一推到底。最后的f(1)=f(2)=1, 因此,这个问题就转换成了,如何求矩阵的幂。当然了,要快速,不然就没有什么意义了。 我们先把问题退化一下,先不考虑求矩阵的幂,而是求一个整数的幂,这个够简单的吧。

先来看看最naive的解法:(方便起见,这里假设n为非负数,不对n小于0的情况做讨论)

ll pow(ll m, ll n){
    ll res = 1;
    for(ll i=0; i<n; ++i)
        res *= m;
    return res;
}

时间复杂度O(n)。现在让我们来考虑一种更快的方法,假设我们要计算m13 , 然后我们把指数13写成二进制形式13=1101,一开始结果res=1.我们要计算的幂可以写成:

m^13 = m^1 * m^4 * m^8

我们可以很直观的得出,如果指数13的二进制形式1101中的某一位为1,那么, res就去乘以那一位对应的一个数。比如,1101从低位起,第1位为1,那么res乘以m1 , 第二位为0,res不需要乘以m2 ,第三位为1,res乘以m4 ,第四位为1,res乘以m8 , 最后得到的就是:

res = m^1 * m^4 * m^8

而且由于每次res去乘以的数(如果该位为0则不乘)都是上一次那个数的平方, 所以,这个数我用完一次,就对它取平方,准备下一次的使用即可。看代码:

ll pow1(ll m, ll n){
    ll res = 1;
    while(n > 0){
        if(n&1) res *= m;
        m *= m;
        n >>= 1;
    }
    return res;
}

时间复杂度O(logn),正是我们想要的快速版本。OK, 这时候如果让你快速求矩阵的幂,是不是很简单了?只需要将实数乘法改成矩阵乘法即可。

void pow(ll s[2][2], ll a[2][2], ll n){
    while(n > 0){
        if(n&1) mul(s, s, a);
        mul(a, a, a);
        n >>= 1;
    }
}

基本上是一模一样的,只不过由于计算结果是矩阵,不能直接用return进行返回, 而是在函数的参数列表中返回。矩阵乘法函数如下(只考虑2*2的矩阵乘法)

void mul(ll c[2][2], ll a[2][2], ll b[2][2]){
    ll t[4];
    t[0] = a[0][0]*b[0][0] + a[0][1]*b[1][0];
    t[1] = a[0][0]*b[0][1] + a[0][1]*b[1][1];
    t[2] = a[1][0]*b[0][0] + a[1][1]*b[1][0];
    t[3] = a[1][0]*b[0][1] + a[1][1]*b[1][1];
    c[0][0] = t[0];
    c[0][1] = t[1];
    c[1][0] = t[2];
    c[1][1] = t[3];
}

于是,求斐波那契数列第n项的O(logn)解法如下:

ll fib2(ll n){
    if(n < 1) return -1;
    if(n == 1 || n == 2) return 1;

    ll a[2][2] = { {1, 1}, {1, 0} };
    ll s[2][2] = { {1, 0}, {0, 1} };
    pow(s, a, n-2);
    return s[0][0] + s[0][1];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值