Time complexity Fibonacci序列的计算复杂性

Time complexity Fibonacci序列的计算复杂性,time-complexity,big-o,complexity-theory,fibonacci,Time Complexity,Big O,Complexity Theory,Fibonacci,我理解大O符号,但我不知道如何计算许多函数的大O符号。特别是,我一直在试图找出斐波那契序列的原始版本的计算复杂性: int Fibonacci(int n) { if (n <= 1) return n; else return Fibonacci(n - 1) + Fibonacci(n - 2); } intfibonacci(intn) { 如果(n只需问问自己需要执行多少语句才能完成F(n) 对于F(1),答案是1(条件的第一部分)

我理解大O符号,但我不知道如何计算许多函数的大O符号。特别是,我一直在试图找出斐波那契序列的原始版本的计算复杂性:

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

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

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

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

那么什么函数满足这些规则呢?请尝试(a>1):

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

除以a(n-2):

a2==a+1

求解
a
,得到
(1+sqrt(5))/2=1.6180339887
,否则称为


因此它需要指数时间。

您将计算
Fib(n)
的时间函数建模为计算
Fib(n-1)
的时间加上计算
Fib(n-2)
的时间加上将它们相加的时间(
O(1)
)。这是假设重复计算相同的
Fib(n)
使用相同的时间-即不使用备忘录


T(n它在下端以
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)

如果您愿意,可以使用its进一步减少紧界限。

关于这一点有一个很好的讨论。在第5页,他们指出,如果您假设一个加法需要一个计算单位,那么计算Fib(N)所需的时间与Fib(N)的结果密切相关

因此,您可以直接跳到非常接近斐波那契级数的近似值:

Fib(N) = (1/sqrt(5)) * 1.618^(N+1) (approximately)
因此,假设naive算法在最坏情况下的性能是

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

PS:如果您想了解更多信息,可以在Wikipedia上讨论over。

好吧,根据我的说法,它是
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)]
的顺序证明答案很好,但我总是要亲自做几次迭代才能真正说服自己。所以我在白板上画了一个小的调用树,开始计算节点。我将计数分为总节点、叶节点和内部节点。我得到的结果如下:

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))

我同意pgaur和rickerbh的观点,递归fibonacci的复杂度是O(2^n)

我通过一个相当简单的推理得出了同样的结论,但我相信这个推理仍然有效

首先,计算递归斐波那契函数(F()从现在开始)在计算第n个斐波那契数时被调用的次数。如果它在序列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    ***************************
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的增长,这个金字塔的底部扩大的速度有多快

让我们举一个真实的例子,例如F(6)


您可以将其展开并进行可视化

     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)
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)

由于计算过程中的重复,朴素递归版本的斐波那契在设计上是指数型的:

在根目录下,您正在计算:

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个递归调用,这些调用在计算中浪费了大量数据,时间函数如下所示:

n              *
n-1            **
n-2           ****  
...
2           ***********
1       ******************
0    ***************************
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];
}
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))

这只是一个下限,对于您的分析来说应该足够了,但实时函数是一个常数的因子,由相同的斐波那契公式计算,已知是黄金比率的指数

此外,您可以使用如下动态规划找到优化版本的Fibonacci:

n              *
n-1            **
n-2           ****  
...
2           ***********
1       ******************
0    ***************************
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];
}
让我们找出作为输入大小函数的步骤数

m = log2(n)
2^m = 2^log2(n) = n
那么,作为输入大小函数的算法成本为:

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

这就是代价是指数的原因。

通过绘制递归树可以更好地估计递归算法的时间复杂度,在这种情况下,绘制递归树的递归关系为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
这里让我们说一下上面树的每一层
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 work
1 2^1
2 2^2
3 2^3..so on
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)