Fibonacci序列的计算复杂性

时间:2008-12-11 20:20:25

标签: time-complexity big-o complexity-theory fibonacci

我理解Big-O表示法,但我不知道如何为许多函数计算它。特别是,我一直试图弄清楚Fibonacci序列的天真版本的计算复杂性:

int Fibonacci(int n)
{
    if (n <= 1)
        return n;
    else
        return Fibonacci(n - 1) + Fibonacci(n - 2);
}

Fibonacci序列的计算复杂度是多少以及如何计算?

13 个答案:

答案 0 :(得分:333)

您可以将时间函数建模为Fib(n),以计算Fib(n-1)的时间加上计算Fib(n-2)的时间加上将它们加在一起的时间(O(1)) 。这假设对同一Fib(n)的重复评估需要相同的时间 - 即不使用任何记忆。

T(n<=1) = O(1)

T(n) = T(n-1) + T(n-2) + O(1)

你解决了这种递归关系(例如使用生成函数),你最终会得到答案。

或者,您可以绘制递归树,其深度为n并直观地确定此函数是渐近O(2 n {{1 }}。然后,您可以通过归纳证明您的猜想。

基地:)很明显

假设n = 1 T(n-1) = O(2 n-1因此

) 等于

T(n) = T(n-1) + T(n-2) + O(1) T(n) = O(2 n-1 ) + O(2 n-2 ) + O(1) = O(2功能 n

然而,正如评论中所指出的,这不是紧张的约束。关于这个函数的一个有趣的事实是T(n)与)的值渐近相同,因为它们都被定义为

Fib(n)

递归树的叶子将始终返回1. f(n) = f(n-1) + f(n-2)的值是递归树中叶子返回的所有值的总和,它等于叶子的数量。由于每个叶子将采用O(1)进行计算,因此Fib(n)等于T(n)。因此,该函数的紧束缚是Fibonacci序列本身(〜Fib(n) x O(1) θ(1.6 n)。你可以通过使用我上面提到的生成函数来找出这个紧密的界限。

答案 1 :(得分:116)

只要问问自己需要执行多少语句才能完成F(n)

对于F(1),答案是1(条件的第一部分)。

对于F(n),答案是F(n-1) + F(n-2)

那么什么功能满足这些规则?尝试 n (a&gt; 1):

a n == a (n-1) + a (n-2)

除以(n-2)

a 2 == a + 1

解析a并获得(1+sqrt(5))/2 = 1.6180339887,也称为golden ratio

因此需要指数时间。

答案 2 :(得分:29)

specific problem over at MIT进行了非常好的讨论。在第5页,他们指出,如果你假设一个加法需要一个计算单位,那么计算Fib(N)所需的时间与Fib(N)的结果密切相关。

因此,您可以直接跳到Fibonacci系列的非常接近的近似值:

Fib(N) = (1/sqrt(5)) * 1.618^(N+1) (approximately)

并且因此说,天真算法的最差情况是

O((1/sqrt(5)) * 1.618^(N+1)) = O(1.618^(N+1))

PS:如果你想了解更多信息,可以在维基百科上讨论closed form expression of the Nth Fibonacci number

答案 3 :(得分:29)

我同意pgaur和rickerbh,recursive-fibonacci的复杂性是O(2 ^ n)。

我以相当简单的方式得出了相同的结论,但我认为仍然是有效的推理。

首先,在计算第N个斐波纳契数时,需要计算出递归的斐波那契函数(从现在开始的F())被调用的次数。如果在序列0到n中每个数字调用一次,那么我们有O(n),如果每个数字被调用n次,那么我们得到O(n * n)或O(n ^ 2),等等。

因此,当为数字n调用F()时,对于0和n-1之间的给定数字,调用F()的次数随着接近0而增加。

作为第一印象,在我看来,如果我们以视觉方式放置它,每次为一个给定数字调用F()时绘制一个单位,湿润得到一种金字塔形状(也就是说,如果我们中心单位水平)。像这样:

n              *
n-1            **
n-2           ****  
...
2           ***********
1       ******************
0    ***************************

现在,问题是,随着n的增长,这个金字塔的基数有多快?

让我们来看一个真实案例,例如F(6)

F(6)                 *  <-- only once
F(5)                 *  <-- only once too
F(4)                 ** 
F(3)                ****
F(2)              ********
F(1)          ****************           <-- 16
F(0)  ********************************    <-- 32

我们看到F(0)被调用了32次,这是2 ^ 5,对于这个样本情况是2 ^(n-1)。

现在,我们想知道F(x)被调用多少次,我们可以看到调用F(0)的次数只是其中的一部分。

如果我们在精神上将所有*从F(6)移动到F(2)线到F(1)线,我们看到F(1)和F(0)线的长度现在相等。这意味着,当n = 6时,调用总次数F()为2x32 = 64 = 2 ^ 6.

现在,就复杂性而言:

O( F(6) ) = O(2^6)
O( F(n) ) = O(2^n)

答案 4 :(得分:15)

您可以展开它并进行可视化

     T(n) = T(n-1) + T(n-2) <
     T(n-1) + T(n-1) 

     = 2*T(n-1)   
     = 2*2*T(n-2)
     = 2*2*2*T(n-3)
     ....
     = 2^i*T(n-i)
     ...
     ==> O(2^n)

答案 5 :(得分:9)

它的下端是2^(n/2),上端是2 ^ n(如其他注释中所述)。递归实现的一个有趣的事实是它具有Fib(n)本身的紧密渐近界。这些事实可以概括为:

T(n) = Ω(2^(n/2))  (lower bound)
T(n) = O(2^n)   (upper bound)
T(n) = Θ(Fib(n)) (tight bound)

如果您愿意,可以使用closed form进一步减少限制。

答案 6 :(得分:9)

证明答案很好,但我总是需要手工做几次迭代才能真正说服自己。所以我在我的白板上画了一个小的调用树,并开始计算节点。我将计数分为总节点,叶节点和内部节点。这就是我得到的:

IN | OUT | TOT | LEAF | INT
 1 |   1 |   1 |   1  |   0
 2 |   1 |   1 |   1  |   0
 3 |   2 |   3 |   2  |   1
 4 |   3 |   5 |   3  |   2
 5 |   5 |   9 |   5  |   4
 6 |   8 |  15 |   8  |   7
 7 |  13 |  25 |  13  |  12
 8 |  21 |  41 |  21  |  20
 9 |  34 |  67 |  34  |  33
10 |  55 | 109 |  55  |  54

立即跳出的是叶子节点的数量是fib(n)。需要多次迭代注意的是内部节点的数量是fib(n) - 1。因此,节点总数为2 * fib(n) - 1

由于在对计算复杂性进行分类时丢弃系数,因此最终答案为θ(fib(n))

答案 7 :(得分:6)

通过绘制递归树可以更好地估计递归算法的时间复杂度,在这种情况下,绘制递归树的递归关系为T(n)= T(n-1)+ T(n-2)+ O(1 ) 请注意,每个步骤都需要O(1)表示恒定时间,因为它只对 if 块中的n值进行了一次比较。递归树看起来像

          n
   (n-1)      (n-2)
(n-2)(n-3) (n-3)(n-4) ...so on

在此可以说,上述树的每个级别都由i表示 因此,

i
0                        n
1            (n-1)                 (n-2)
2        (n-2)    (n-3)      (n-3)     (n-4)
3   (n-3)(n-4) (n-4)(n-5) (n-4)(n-5) (n-5)(n-6)

让我们说树以i的特定值结束,这种情况将在n-i = 1时出现,因此i = n-1,这意味着树的高度为n-1。 现在让我们看一下树中n层每一层要完成多少工作。请注意,按照递归关系所述,每个步骤都需要O(1)时间。

2^0=1                        n
2^1=2            (n-1)                 (n-2)
2^2=4        (n-2)    (n-3)      (n-3)     (n-4)
2^3=8   (n-3)(n-4) (n-4)(n-5) (n-4)(n-5) (n-5)(n-6)    ..so on
2^i for ith level

因为i = n-1是在每个级别完成的树工作的高度,所以

i work
1 2^1
2 2^2
3 2^3..so on

因此,完成的总工作量将是每个级别的工作总和,因此,由于i = n,它将是2 ^ 0 + 2 ^ 1 + 2 ^ 2 + 2 ^ 3 ... + 2 ^(n-1) -1。 按几何级数,该总和为2 ^ n,因此这里的总时间复杂度为 O(2 ^ n)

答案 8 :(得分:2)

嗯,据我说它是O(2^n)因为在这个函数中只有递归需要相当长的时间(分而治之)。我们看到,当我们达到F(n-(n-1))F(1)时,上面的函数将在树中继续,直到叶子接近为止。所以,这里当我们记下在树的每个深度遇到的时间复杂度时,求和系列是:

1+2+4+.......(n-1)
= 1((2^n)-1)/(2-1)
=2^n -1

2^n [ O(2^n) ]的顺序。

答案 9 :(得分:0)

答案 10 :(得分:0)

由于计算中的重复,Fibonacci的朴素递归版本是指数式的:

你正在计算根:

F(n)取决于F(n-1)和F(n-2)

F(n-1)再次取决于F(n-2)和F(n-3)

F(n-2)再次取决于F(n-3)和F(n-4)

然后你在每个级别进行2次递归调用,这些调用在计算中浪费了大量数据,时间函数将如下所示:

T(n)= T(n-1)+ T(n-2)+ C,C常数

T(n-1)= T(n-2)+ T(n-3)> T(n-2)然后

T(n)> 2 * T(N-2)

...

T(n)> 2 ^(n / 2)* T(1)= O(2 ^(n / 2))

这只是一个下限,为了你的分析目的应该足够,但实时函数是一个常数因子同一个斐波纳契公式,而closed form已知是指数的黄金比。

此外,您可以使用动态编程找到Fibonacci的优化版本,如下所示:

static int fib(int n)
{
    /* memory */
    int f[] = new int[n+1];
    int i;

    /* Init */
    f[0] = 0;
    f[1] = 1;

    /* Fill */
    for (i = 2; i <= n; i++)
    {
        f[i] = f[i-1] + f[i-2];
    }

    return f[n];
}

这是优化的,只做 n 步骤,但也是指数级的。

从输入大小到解决问题的步骤数定义成本函数。当您看到Fibonacci的动态版本( n 计算表格的步骤)或最简单的算法来了解数字是否为素数( sqrt(n)来分析有效除数的数量)。你可能认为这些算法是 O(n) O(sqrt(n)),但由于以下原因,这不是正确的: 算法的输入是一个数字: n ,使用二进制表示法,整数 n 的输入大小为 log2(n)然后执行

的变量
m = log2(n) // your real input size

让我们根据输入大小找出步数

m = log2(n)
2^m = 2^log2(n) = n

然后算法的成本与输入大小的函数是:

T(m) = n steps = 2^m steps

这就是成本是指数的原因。

答案 11 :(得分:0)

通过绘制函数调用来进行计算很简单。只需为n的每个值添加函数调用,然后看数字如何增长。

大O是O(Z ^ n),其中Z是黄金比例或大约1.62。

随着n的增加,Leonardo数和Fibonacci数均接近该比率。

与其他Big O问题不同,输入中没有可变性,并且算法和算法实现均已明确定义。

不需要一堆复杂的数学运算。只需简单地画出下面的函数调用,然后将函数拟合为数字即可。

或者,如果您熟悉黄金分割率,那么您会认识到它。

此答案比公认的答案更正确,后者声称它将接近f(n)= 2 ^ n。它永远不会。它将接近f(n)= golden_ratio ^ n。

2 (2 -> 1, 0)

4 (3 -> 2, 1) (2 -> 1, 0)

8 (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
            (2 -> 1, 0)


14 (5 -> 4, 3) (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
            (2 -> 1, 0)

            (3 -> 2, 1) (2 -> 1, 0)

22 (6 -> 5, 4)
            (5 -> 4, 3) (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
                        (2 -> 1, 0)

                        (3 -> 2, 1) (2 -> 1, 0)

            (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
                        (2 -> 1, 0)

答案 12 :(得分:-4)

这样做的方式更好:

unsigned int Fib(unsigned int n)
{
    // first Fibonaci number is Fib(0)
    // second one is Fib(1) and so on

    // unsigned int m;  // m + current_n = original_n
    unsigned int a = 1; // Fib(m)
    unsigned int b = 0; // Fib(m-1)
    unsigned int c = 0; // Fib(m-2)

    while (n--)
    {
        c = b;
        b = a;
        a = b+c;
    }

    return a;
}