【问题标题】:Can O(N*N) be faster than O(N)O(N*N) 能比 O(N) 快吗
【发布时间】:2015-10-26 18:16:32
【问题描述】:

谁能给我一个现实的例子,其中O(N*N) 算法比O(N) 算法对于某些N>10 更快。

编辑:我看到这个问题因为过于笼统而被搁置。但我只有一个一般性的问题。没有其他方法可以以不同的方式提出这个问题。

【问题讨论】:

  • 这是课堂作业吗?
  • 不是O(n^2) vs O(N),但是当数据的大小较小时,冒泡排序比快速排序更快,因为快速排序的开销超过了冒泡排序的速度。跨度>
  • 答案不是在后面吗?如果你只是在这里问,做这些练习对你有什么帮助?
  • 是:计数排序,当元素的数量很少,但范围很大时(因为你需要迭代直方图中的所有条目)。例如,考虑对元素4,8,6,2,999999 进行计数排序。您需要扫描一百万个条目的数组才能完成这种类型的排序,但您可以在大约 5*5 次迭代中执行任何“标准”排序。
  • 另一个 stroustrup gem 是 O(1) 列表操作,而不是 O(N) 向量操作,其中列表要慢得多(由于缓存)

标签: algorithm big-o


【解决方案1】:

可能是一些人试图使 O(N*N) 算法更快(例如,通过对数据进行一些预处理)并最终得到如下结果:

O(N):

for (int i=0;i<N;i++){
    // do some expensive preconditioning of your data
    // to enable using O(N) algorithm
}
for (int i=0;i<N;i++){
    // run the actual O(N) algorithm
}

O(N*N):

for (int i=0;i<N;i++){
    for (int j=0;j<N;j++){
        // run the O(N*N) algorithm
    }
}

大 O 表示法只是大 N 的限制行为。常数(或线性)部分可能有很大差异。例如可能是这样的

O(N)   =           N + 10000 
O(N*N) = N^2 + 0.5*N +    10

【讨论】:

  • “大 O 表示法只是大 N 的限制行为。常数(或线性)部分可能有很大差异。” -- 我不明白你在这里想说什么。
  • @DavidTitarenco 更好吗?
  • 一个很好的例子可能是在数组中查找重复项。将所有元素添加到计数集并然后检查计数 > 1 是 O(n),但它比通常很早就发现重复项的基于比较的简单算法要慢。 (例如,因为字母表中只有 26 个字母)。同样,MergeSort 使用 InsertionSort 处理小的子问题。 big-O 表示法仅告诉您哪种算法将在 N=infinity 获胜。
  • 为什么在没有 cmets 的情况下投反对票?如果我知道出了什么问题,我会修复,但由于我不知道要修复什么,我可能会简单地删除它......
  • @Peter Cordes:所以底线是常量可能使 O(NN) 比 O(N) 工作得更好。大 O 只是告诉最坏的情况。这并不意味着 O(NN) 将永远是 O(N*N)。例如我试图在数百万个元素的数组中找到一个数字。最坏的情况是要找到的数字在最后。但这个数字仍然可能在开始。这是所有答案的要点吗?我的理解正确吗?
【解决方案2】:

谁能给我一个现实的例子,其中 O(N*N) 算法比 O(N) 算法快一些 N>10。

大 O 表示法仅描述算法的渐近性能,N 趋于正无穷。

最重要的是:它描述了算法的理论性能——而不是它的实际实现!

这就是为什么在大 O 符号中省略了与其他开销相关的常量和次要函数。它们与主函数的形状无关(尤其是当 N 趋于无穷大时) - 但它们对于分析算法实现的实际性能至关重要。

简单的例子。将sleep(60) 放入qsort() 函数中。渐近算法仍然是相同的O(N*log(N)) 算法,因为与无穷大相比,恒定的 60 秒睡眠是微不足道的。但实际上,这样的qsort() 实现将被任何冒泡排序实现所超越(当然没有sleep()),因为现在N*log(N) 前面是巨大 常量。 p>

【讨论】:

    【解决方案3】:

    输入是一个整数 n。

    第一个例子:一对短程序使用 O 表示法是一个上限这一事实,所以一个 O(1) 的程序也是 O(n) 和 O(n^2),等等......

    方案一:

    def prog1(n)
        1.upto(n) do |i|
        end
    end
    

    方案二:

    def prog2(n)
        return
    end
    

    程序 1 是 O(n),程序 2 是 O(n*n) 以及 O(n) 和 O(1) 和 O(n^n^n^n^n)。

    但程序 2 比程序 1 快。

    第二个例子:一对程序使用 O 表示法依赖于 n 变大时的行为这一事实。

    方案一:同上

    方案二:

    def prog2(n)
        if n < 10^100
            return
        else
            1.upto(n) do |i|
                1.upto(n) do |j|
                end
            end
        end
    end
    

    程序 1 是 O(n),程序 2 是 O(n*n)。

    但在 n >= 10^100 之前,程序 2 比程序快。

    【讨论】:

    • 不,我不明白你的第二个程序 O(n*n) 怎么样?它与你得到的 arg 无关。
    • 因为 O(n) 中的所有内容都在 O(n^2) 中,而 O(1) 中的所有内容都在 O(n) 中
    • @anurag86 O 表示法给出了一个上限。 Theta 给出了一个紧密的界限。欧米茄给出了一个上限。如果某物是 O(f(n)),那么对于所有 g >= f (即 lim n -> g(n)/f(n) >=1的无穷大,它也是 O(g(n)) >
    • 这是一个正确但无用的答案。显然,OP 认为 O(n^2) 是 O(n^2) 算法运行时间的严格上限。如果你想挑剔使用 big-O 而不是 big-Omega,你应该这么说。不需要示例代码。
    • @PeterCordes 我不同意。有人对 big-O 的含义感到困惑,而且其中有很多,如果他们看到一个具体的例子,他们更有可能知道这是一个上限,尽管我应该在我的回答中而不是在评论中这么说。跨度>
    【解决方案4】:

    作为一个实际的例子,请参阅my answerthis question

    N 个数字的中位数可以在 O(N) 时间(最坏情况下或平均情况下)找到。比如在std::nth_element中实现了这样一个算法。

    然而,对于小的 N,具有 O(N^2) 复杂度的建议算法可以运行得更快,因为 1)它没有分支,2)它可以用 SSE 向量化。至少,对于short 类型的N=23 个元素,它的性能优于std::nth_element 4-5 倍。此特定设置在图像处理中具有应用。

    P.S. 顺便说一下,std::nth_element 的实现通常在 N 时在内部使用插入排序,这也是一个 O(N^2 ) 算法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-25
      • 1970-01-01
      • 2020-01-15
      • 1970-01-01
      • 1970-01-01
      • 2021-12-22
      • 1970-01-01
      • 2019-10-23
      相关资源
      最近更新 更多