您的位置:首页 > 其它

Fibonacci 数列O(logn)解法

2017-08-07 20:07 330 查看


传统解法

提到斐波那契数列(Fibonacci Sequence),首先想到的是经典的动规(DP)算法。

时间复杂度O(n),这里空间复杂度可以优化到O(1)。代码如下:

int fib_n(int n)
{
int dp[3] = {1, 1};
if (n <= 1) return dp
;

for (int i = 2; i <= n; ++i)
dp[i % 3] = dp[(i - 1) % 3] + dp[(i - 2) % 3];

return dp[n % 3];
}


 但是初次接触O(logn)解法有如醍醐灌顶,叹为观止……


O(logn)解法


思路来源 1

考虑一个求幂运算。比如求an,一般来说需要n次累乘,时间复杂度显然是O(n)。实际上可以通过递归达到一种更优化的效果:

an = (an/2)2 * an%2

这里的n/2是取整,如5/2=2。这样就可以实现相当于二分的效果,时间复杂度为O(logn)。


思路来源2

对于Fib数列an(n ≥ 0),可以通过矩阵乘法的方式进行递推:



进而可以得到:



这样就(很机智地)把Fib数列问题转化成了一个求矩阵幂的运算。


解题方法

结合以上思路,首先将其转化为矩阵求幂问题,然后进行二分,O(logn)解法由此诞生。再次感慨人类清奇的脑洞 _(:з」∠)_

以下是代码:

int** mult(int** m1, int** m2)

{

    int** res = new int*[2];

    for (int i = 0; i < 2; ++i) res[i] = new int[2];

    res[0][0] = m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0];

    res[0][1] = m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1];

    res[1][0] = m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0];

    res[1][1] = m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1];

    return res;

}

int** recur(int x)

{

    if (x == 0) {

        int** res = new int*[2];

        for (int i = 0; i < 2; ++i) res[i] = new int[2];

        res[0][0] = res[1][1] = 1;

        res[0][1] = res[1][0] = 0;

        return res;

    }

    if (x == 1) {

        int** res = new int*[2];

        for (int i = 0; i < 2; ++i) res[i] = new int[2];

        res[0][1] = res[1][0] = res[1][1] = 1;

        res[0][0] = 0;

        return res;

    }

    int** half = recur(x / 2);

    return mult(mult(half, half), recur(x % 2));

}

// time: O(logn)

int fib_logn(int n)

{

    if (n == 0 || n == 1) return 1;

    int** mat = recur(n - 1);

    return mat[0][1] + mat[1][1];

}


结果比较

简单比较一下后者的优化效果,为了是效果更明显,这里将参数设置成一个较大的数(如109),以下是代码以及结果:

void test()
{
const int num = 1e9;
clock_t t1, t2;

t1 = clock();
fib_n(num);
t2 = clock();
printf("O(n): %.4f s\n", (double)(t2 - t1) / CLOCKS_PER_SEC);

t1 = clock();
fib_logn(num);
t2 = clock();
printf("O(logn): %.4f s\n", (double)(t2 - t1) / CLOCKS_PER_SEC);

}


 结果



O(n)算法的速度达到了男子百米的世界顶级水平,而O(logn)只表现出一脸不屑……

好吧,我服……那我把logn的多跑几次 for (int i
= 0; i < 100; ++i) fib_logn(num); 

 


那么结果也很明显了,O(logn)算法表现惊艳!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: