【问题标题】:Why is the complexity of computing the Fibonacci series 2^n and not n^2?为什么计算斐波那契数列的复杂度是 2^n 而不是 n^2?
【发布时间】:2011-11-24 17:13:06
【问题描述】:

我正在尝试使用递归树来查找斐波那契数列的复杂性,并得出结论height of tree = O(n)最坏情况cost of each level = cn,因此complexity = n*n=n^2

怎么会是O(2^n)

【问题讨论】:

  • 它是 theta (F_N) (所以 O(2^N)),即使你认为将两个 n 位数字相加为 N 中的多项式的复杂性,我相信。
  • 作为后续问题,如果我们将每个 $F_k$ 一直存储到 $F_n$,时间复杂度是否等于 $n^2$?因为在这种情况下,我们只执行了总共 $n$ 次加法,其中 $F_k$ 的长度是 $O(k)$。

标签: algorithm recursion big-o fibonacci


【解决方案1】:

朴素递归斐波那契的复杂度确实是 2ⁿ。

T(n) = T(n-1) + T(n-2) = T(n-2) + T(n-3) + T(n-3) + T(n-4) = 
= T(n-3) + T(n-4) + T(n-4) + T(n-5) + T(n-4) + T(n-5) + T(n-5) + T(n-6) = ...

在每个步骤中,您两次调用T,因此将提供最终的渐近障碍:
T(n) = 2⋅2⋅...⋅2 = 2ⁿ

奖励:斐波那契的最佳理论实现实际上是close formula,使用golden ratio

Fib(n) = (φⁿ – (–φ)⁻ⁿ)/sqrt(5) [where φ is the golden ratio]

(但是,由于浮点运算,它在现实生活中会出现精度误差,不精确)

【讨论】:

  • 如果我们有recusion T(n) = T(n/2)+T(n/2) 那么复杂度是2^n/2。如果我错了请纠正我?跨度>
  • @Suri:在您的示例中 [在评论中] 它是不同的,因为 n 在 T(n) 中也呈指数下降:T(n) = T(n/2) + T(n/2) = T(n/4) + T(n/4) + T(n/4) + T(n/4) = ... = T(n/2^logn) + ... + T(n/2^logn) [2^logn times] = 2^logn = n
  • @amit- 请注意,当您调用 T 两次时,它不在同一级别上,并且 2^n 不是一个紧密的界限。例如,要计算 F(n),您只需计算 F(n - 1) 一次。
  • @templatetypedef:我故意避免使用“紧”或“完全”这个词,因为显然不是这样。这个答案甚至没有证明渐近界,它只是为了向 OP 展示一个基本工具来粗略地评估这种复杂性。
  • @amit 不是 T(n) = 2+2^2+2^3+2^4 ⋅...⋅2 = 2ⁿ 而不是 T(n) = 2⋅2⋅ ...⋅2 = 2ⁿ 。这是为了澄清我的理解。我们不是在递归树的每一层添加节点的总和吗?
【解决方案2】:

这样看。假设通过递归计算F(k)kth 斐波那契数的复杂度最多为2^k,对于k <= n。这是我们的归纳假设。那么递归计算F(n + 1)的复杂度为

F(n + 1) = F(n) + F(n - 1)

具有复杂性2^n + 2^(n - 1)。请注意

2^n + 2^(n - 1) = 2 * 2^n / 2 + 2^n / 2 = 3 * 2^n / 2 <= 2 * 2^n = 2^(n + 1).

我们已经通过归纳证明,通过递归计算F(k) 最多为2^k 的说法是正确的。

【讨论】:

  • 你归纳的依据在哪里?没有基础,我几乎可以通过归纳证明任何事情。
  • @amit- 是的,你完全正确。我要说明的一点是,通过归纳任何 f(n) 来证明运行时是 O(f(n)) 是不够的,而且你必须给出一个你想要的显式函数证明运行时间永远不会超过。但绝对在这种情况下,您可以显示 2^n 的界限。
  • @templatetypedef:我理解你的意思,但你是说我理解错了吗?
  • @AndreyT:你看过数学杂志吗?你知道“留给阅读练习”、“证明是显而易见的”等短语吗?
  • @Jason:呃...我有点惊讶我必须解释一些简单的东西,但无论如何...鉴于这个讨论已经持续了一段时间,我认为您会同意重要的是您的帖子是否有被否决。目前没有反对票的事实几乎无关紧要。 反对票,不是吗?
【解决方案3】:

你是正确的,树的深度是 O(n),但你没有在每个级别上做 O(n) 的工作。在每一层,每个递归调用都做 O(1) 的工作,但是每个递归调用会贡献两个新的递归调用,一个在它的下一层,一个在它下面的第二层。这意味着,随着您在递归树中越走越远,每个级别的调用次数呈指数级增长。

有趣的是,您实际上可以将计算 F(n) 所需的准确调用次数确定为 2F(n + 1) - 1,其中 F(n) 是第 n 个斐波那契数。我们可以归纳地证明这一点。作为基本情况,要计算 F(0) 或 F(1),我们需要对函数进行一次调用,该函数在不进行任何新调用的情况下终止。假设 L(n) 是计算 F(n) 所需的调用次数。然后我们就有了

L(0) = 1 = 2*1 - 1 = 2F(1) - 1 = 2F(0 + 1) - 1

L(1) = 1 = 2*1 - 1 = 2F(2) - 1 = 2F(1 + 1) - 1

现在,对于归纳步​​骤,假设对于所有 n'

1 + L(n - 1) + L(n - 2)

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

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

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

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

= 2F(n + 1) - 1

这完成了归纳。

此时,您可以使用Binet's formula 来表明

L(n) = 2(1/√5)(((1 + √5) / 2)n - ((1 - √5) / 2)n) - 1

因此 L(n) = O(((1 + √5) / 2)n)。如果我们使用约定

φ = (1 + √5) / 2 & 约; 1.6

我们有这个

L(n) = Θ(φn)

并且由于 φ n)(使用 little-o 表示法)。

有趣的是,我为这个系列选择了名称 L(n),因为这个系列被称为 Leonardo numbers。除了这里的使用,它还出现在smoothsort算法的分析中。

希望这会有所帮助!

【讨论】:

  • 感谢您的回复,我理解您的意思,但如果我们有递归 T(n) = T(n/2)+T(n/2),那么复杂度将是 2^n/2。请如果我错了,请纠正我?
  • @Suri- 我相信,递归 T(n) = 2T(n / 2), T(1) = 1 求解为 O(n)。您应该将其作为一个单独的问题发布,以便人们可以为您提供更详细的答案。
  • Downvoter-您能解释一下这个答案有什么问题吗?我相信它在数学上是正确的,并且确实回答了这个问题。如果我对此有误,请告诉我我可以做些什么来改进答案。
  • @templatetypedef:是的,确实如此。如果T(k) 的复杂度对于k &lt;= n - 1 至多为k,那么T(n) 的复杂度至多为T(n) = T(n / 2) + T(n / 2) &lt;= 2 * n / 2 = n
  • 我喜欢这个解决方案。在 Java 中只是:double phi = 1.6180339887; return Math.round((Math.pow(phi, n) / Math.sqrt(5)));
【解决方案4】:

斐波那契数列的复杂度为 O(F(k)),其中 F(k) 是第 k 个斐波那契数。这可以通过归纳来证明。对于基础案例来说这是微不足道的。并假设对于所有k

【讨论】:

  • 一般来说,这种说法是行不通的,因为你必须非常精确地知道大 O 术语中的常数因子是什么。用 big-O 进行归纳可以很容易地让你证明完全不正确的结果,每一步的数学都是正确的,但是因为你在 big-O 项中隐藏了越来越大的常数,你最终证明了一些快速增长的东西实际上没有。为了更正式地证明这一点,您实际上必须提出常数 n0 和 c。
  • @template:你注意到small了吗? smallOh 和 BigOh 有很大的区别...
  • @user127.0.0.1- 我不相信这会改变这里的情况;一个类似有缺陷的归纳证明可以这样制作。再说一次,我的抱怨不是结果,而是方法。
  • @template:我主要是指出你对常量的评论不适用于这个证明。当然,这个证明是有缺陷的。从根本上说,当您将自己限制在有限 n 时,谈论渐近是毫无意义的。 (即声明T(k) = C*F(k) + o(F(k)) for k &lt;= n 毫无意义)。
【解决方案5】:

t(n)=t(n-1)+t(n-2) 可以通过tree方法解决:

                                  t(n-1)  +  t(n-2)        2^1=2
                                   |         |  
                            t(n-2)+t(n-3)  t(n-3)+t(n-4)   2^2=4  
                                .               .          2^3=8
                                .               .           .
                                .               .           .

最后一层也是如此。 . 2^n
它会使总时间复杂度=>2+4+8+.....2^n 解决上述 gp 后,我们将得到时间复杂度为 O(2^n)

【讨论】:

    【解决方案6】:

    fib(n) 的递归树类似于:

                                  n       
                               /     \
                              n-1    n-2      --------- maximum 2^1 additions
                             /  \    /   \
                           n-2   n-3 n-3 n-4  -------- maximum 2^2 additions
                          /   \           
                         n-3 n-4              -------- maximum 2^3 additions         
                                                    ........
                                              -------- maximum 2^(n-1) additions  
    
    1. 在 2^(n-1) 中使用 n-1,因为对于 fib(5),我们最终会下降到 fib(1)
    2. 内部节点数 = 叶数 - 1 = 2^(n-1) - 1
    3. 添加数 = 内部节点数 + 叶数 = (2^1 + 2^2 + 2^3 + ...) + 2^(n-1)
    4. 我们可以将内部节点的数量替换为 2^(n-1) - 1,因为它总是小于这个值: = 2^(n-1) - 1 + 2^(n-1) ~ 2^n

    【讨论】:

      【解决方案7】:

      斐波那契数计算的 O(2^n) 复杂度仅适用于递归方法。使用一些额外的空间,您可以使用 O(n) 获得更好的性能。

      public static int fibonacci(int n) throws Exception {
      
        if (n < 0)
          throws new Exception("Can't be a negative integer")
      
        if (n <= 1)
          return n;
      
        int s = 0, s1 = 0, s2 = 1;
        for(int i= 2; i<=n; i++) {
          s = s1 + s2;
          s1 = s2;
          s2 = s;            
        }
        return s;
      }
      

      【讨论】:

        【解决方案8】:

        递归斐波那契数列的复杂度为 2^n:
        这将是递归斐波那契的递归关系

         T(n)=T(n-1)+T(n-2)                  No  of elements  2
        

        现在使用替换方法解决这个关系(替换 T(n-1) 和 T(n-2) 的值)

        T(n)=T(n-2)+2*T(n-3)+T(n-4)          No  of elements  4=2^2
        

        再次代入上述项的值,我们将得到

        T(n)=T(n-3)+3*T(n-4)+3*T(n-5)+T(n-6)  No  of elements  8=2^3
        

        完全解决后,我们得到

        T(n)={T(n-k)+---------+---------}----------------------------->2^k  eq(3) 
        

        这意味着任何级别的最大递归调用次数最多为 2^n。
        对于等式 3 中的所有递归调用是 ϴ(1),因此时间复杂度将为 2^n* ϴ(1)=2^n

        【讨论】:

          【解决方案9】:

          我无法抗拒将 Fib 的线性时间迭代算法连接到指数时间递归算法的诱惑:如果有人阅读 Jon Bentley 关于“编写高效算法”的精彩小书,我相信这是一个简单的“缓存”案例:每当计算 Fib(k) 时,将其存储在数组 FibCached[k] 中。每当调用 Fib(j) 时,首先检查它是否缓存在 FibCached[j] 中;如果是,则返回值;如果不使用递归。 (现在看看调用树...)

          【讨论】:

            猜你喜欢
            • 2019-05-15
            • 2020-03-22
            • 2018-12-13
            • 1970-01-01
            • 2020-02-12
            • 1970-01-01
            相关资源
            最近更新 更多