对于第N个Fibonacci数,这个O(log n)迭代方法如何工作?

时间:2017-07-22 14:02:00

标签: algorithm fibonacci

我刚刚解决了a problem in Codechef,需要在O(log n)中找到第N个Fibonacci数。我使用了here提到的快速加倍方法:

F(2N)   = F(N) * ( 2F(N+1) - F(N) )
F(2N+1) = F(N+1)^2 + F(N)^2

以下是我的代码示例。迭代版本在上面给出的链接中的代码示例中给出:

快速加倍递归

#include <map>
#include <iostream>
using namespace std;

#define long long long
const long M = 1000000007; // modulo
map<long, long> m;

long F(long n)
 {
    if(m.count(n))return m[n];

    long a, b;
    if((n&1) == 0)
    {
        a = F(n/2)%M;
        b = F((n/2) + 1)%M;
         return m[n] = (((2*a*b) % M - (a*a)%M) + M) % M;
     }
    else
    {
        a = F((n+1)/2)%M;
        b = F((n-1)/2)%M;
        return m[n] = ((a*a) % M + (b*b)% M) % M;
    }
 }

int main()
{
    m[0] = 0;
    m[1] = m[2] = 1;
    printf("%lld", F(100000000));
}

快速加倍迭代

#include <iostream>
using namespace std;

#define long long long
const long MOD = 1000000007;

long F(long n)
{
    long a = 0, b = 1, d, e, c;
    int i = 31;
    while(i >= 0)
    {
        d = (a * (((b * 2 - a) + MOD) % MOD)) % MOD;
        e = ((a * a) % MOD + (b * b) % MOD) % MOD;
        a = d;
        b = e;
        if(((n >> i) & 1) != 0)
        {
            c = (a + b) % MOD;
            a = b;
            b = c;
        }
        i--;
    }

    return a;
}

int main()
{
    printf("%lld", F(1000000000));
} 

使用前者的解决方案获得了TLE,而后者是AC。

现在,我的问题是:

  • 除了&#34;递归开销之外还有其他什么吗?这使得递归解决方案变得更慢 - 就像使用std :: map一样?或者
  • 迭代方法究竟有效吗?虽然这两种方法都使用快速加倍公式,但迭代版本的执行对我来说还不清楚。

有人可以向我解释迭代版本的执行吗?

1 个答案:

答案 0 :(得分:1)

关于您的第一个问题:主要问题是您使用std::map。插入和查找,因为std::map implements a red-black tree(一种二叉搜索树)是O(log n)

  

std::map是一个包含键值的有序关联容器   配有唯一键。使用比较对键进行排序   功能比较。搜索,删除和插入操作具有   对数复杂度。地图通常实现为红黑色   树。

(更不用说与STL的比较功能,根据我的经验,非常慢。)

因此,实际上,您的递归算法会调用另一个log n算法log n次,使其成为O(log n * log(log n))。 (说BST是O(log m),其中m是元素的数量log n,它变为O(log(log n))。)这本身并不是一个很大的区别,但是计算每次操作的成本相对较高。其中大部分归结为性能优化(或不优化)。此外,递归函数的计算迭代次数更多,因为它不像迭代函数那样严格二进制,即使两者具有相同的复杂度。

关于你的第二个问题:我不熟悉“快速加倍”算法背后的数学,所以我不能给出数学运算方式的逐行解释。如图所示,迭代算法的方法是仅通过使用2的幂的Fibonaccis而不是像递归算法那样划分来组成最终结果。它从LSB开始,并且向上运行。