【问题标题】:Do these two nested loops really have the same quadratic time complexity?这两个嵌套循环真的具有相同的二次时间复杂度吗?
【发布时间】:2014-06-03 16:36:15
【问题描述】:

这是我提出的算法的一部分:

for (int i = 0; i < n - 1; i++)
   for (int j = i; j < n; j++)
      (...)

我正在使用这个“双循环”来测试一个大小为 n 的数组中所有可能的 2 元素和。

显然(我不得不同意),这个“双循环”是O(n²)

n + (n-1) + (n-2) + ... + 1 = sum from 1 to n = (n (n - 1))/2

这是我困惑的地方:

for (int i = 0; i < n; i++)
   for (int j = 0; j < n; j++)
      (...)

第二个“双循环”的复杂度也为O(n²),显然(在最坏的情况下)它比第一个好很多(?)。

我错过了什么?信息准确吗?有人能解释一下这个“现象”吗?

【问题讨论】:

标签: algorithm time-complexity


【解决方案1】:

(n (n - 1))/2 简化为 n²/2 - n/2。如果你对n 使用非常大的数字,n/2 的增长率与 相比将相形见绌,所以为了计算 Big-O 复杂度,你实际上忽略了它。同样,随着n 的增加,1/2 的“恒定”值根本不会增加,所以你也忽略了它。剩下的就是

请记住,复杂性计算与“速度”不同。一种算法可能比另一种慢五千倍,但 Big-O 复杂度仍然较小。但是当您将n 增加到非常大的数字时,会出现通常可以使用简单公式进行分类的一般模式:1log nnn log n 等。

有时创建图表并查看出现的线条类型会有所帮助:

尽管这两张图的缩放系数差别很大,但你可以看到它产生的曲线类型几乎完全一样。

【讨论】:

  • 所以基本上,我没有理由使用第一个 n^2 - n/2 算法而不是第二个 n^2 算法? (因为它们是渐近相同的)
  • @Werner:我在回答中添加了更多信息。虽然两者是渐近相同的,但这并不意味着速度没有差异:它只是意味着当你获得更大的数字时,速度差异不太可能重要:比如第一个需要 1,000,000 秒运行,并且第二个只需要 499,500 秒。第二种算法会快一倍,但是有多少人愿意等 5 天让他们的程序运行,谁不愿意等 10 天呢?要知道在实践中使用哪种算法,您需要比 Big-O 数更多的信息。
  • 我不得不争辩说,如果可以选择,没有人会等待 x 次,而他们可以选择只等待 ~x/2 次......所以 big-O 不会一个有用的数字来帮助我决定使用这两种算法中的哪一种?例如,它仅适用于“截然不同”Os (O(n) vs O(n^2)) 的算法......?
  • @Werner:首先,我不在乎是否必须等待 10 微秒而不是 5 微秒才能获得所需的信息。这两个选项都足够快。同样,等待 5 天也太长了,即使替代方案是等待 10 天。 (10 秒对我来说通常太长了!)Big-O 很有用,因为它有助于识别在更可能重要的大型数据集上渐近更快的算法。如果没有 Big-O 差异,那么一种算法可能仍然会快得多,但它不太重要:要么给我找一个可以在
  • @Werner:所以在这种情况下,Big-O 仅在它告诉您如果 N 变得非常高时,这两种算法都会变得非常缓慢的情况下才有用。如果您想知道哪一个真的更快,您很可能需要在真实数据集上测试它们。但无论如何,这通常是一个好的经验法则:“过早的优化是万恶之源。”
【解决方案2】:

常数因子。

Big-O 表示法忽略了常数因子,因此即使第二个循环慢了一个常数因子,但它们最终具有相同的时间复杂度。

就在the definition 中,它告诉您可以选择任何旧的常数因子:

...当且仅当存在一个正常数 M ...

这是因为我们想要分析算法的增长率 - 常数因素只会使事情复杂化,并且通常取决于系统(不同机器上的操作可能持续时间不同)。

可以只计算某些类型的操作,但接下来的问题是选择哪个操作,以及如果该操作在某些算法中不占优势怎么办。然后,您需要将操作相互关联(以独立于系统的方式,这可能是不可能的),或者您可以为每个操作分配相同的权重,但这将是相当不准确的,因为某些操作需要的时间比其他人。

O(15n² + 568n + 8 log n + 23 sqrt(n) + 17)(例如)真的有用吗?而不仅仅是O(n²)


(出于以下目的,假设n &gt;= 2

请注意,我们实际上有渐近更小的项(即随着我们接近无穷大而更小),但我们总是可以将其简化为常数因子的问题。 (It's n(n+1)/2, not n(n-1)/2)

n(n+1)/2 = n²/2 + n/2
       and
n²/2 <= n²/2 + n/2 <= n²

鉴于我们刚刚展示了n(n+1)/2 位于C.n²D.n² 之间,对于两个常量CD,我们也刚刚展示了它是O(n²)

注意 - big-O 表示法实际上是一个严格的上限(所以我们只关心它是否小于一个函数,而不是介于两者之间),但它通常用于表示 Θ (big-Theta),它关心两个界限。

【讨论】:

  • @Paul 我可以简化为常数因子的差异。渐近 n² - n &lt; 2n².
【解决方案3】:

来自The Big O page on Wikipedia

在典型用法中,不使用 O 符号的正式定义 直接地;相反,函数 f 的 O 表示法是由 以下简化规则:
如果 f(x) 是多项的和,则 保留增长率最大的,其他的都省略

Big-O 仅用于给出渐近行为 - 一个比另一个不进入它快一点 - 它们都是 O(N^2)

【讨论】:

    【解决方案4】:

    你也可以说第一个循环是 O(n(n-1)/2)。 big-O 的花哨数学定义类似于:

    函数“f”是函数“g”的大O,如果存在常数c,n使得对于某些c和所有x > n,f(x)

    这是一种奇特的说法 g 是超过某个点并应用了一些常数的上限。然后得出 O(n(n-1)/2) = O((n^2-n)/2) 是 O(n^2) 的大 O,这对于快速分析来说更简洁。

    【讨论】:

      【解决方案5】:

      AFAIK,你的第二个代码 sn-p

      for(int i = 0; i < n; i++) <-- this loop goes for n times
           for(int j = 0; j < n; j++) <-- loop also goes for n times
                (...)
      

      所以本质上,它的时间复杂度为O(n*n) = O(n^2)

      根据 BIG-O 理论,忽略常数因子,只考虑高阶。也就是说,如果复杂度为O(n^2+k),那么实际复杂度将为O(n^2),常量k 将被忽略。

      (OR) 如果复杂度为O(n^2+n),那么实际复杂度将为O(n^2) 低阶n 将被忽略。

      因此,在您的第一个复杂性为 O(n(n - 1)/2) 的情况下,将/可以简化为

      O(n^2/2 - n/2) = O(n^2/2) (Ignoring the lower order n/2)
      = O(1/2 * n^2)
      = O(n^2) (Ignoring the constant factor 1/2)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-28
        • 1970-01-01
        相关资源
        最近更新 更多