【问题标题】:Algorithm Speed Order of算法速度顺序
【发布时间】:2010-10-06 15:30:05
【问题描述】:

有时我在试图用 O(x) 表示法估计算法的速度时完全被愚弄了,我的意思是,我真的可以指出顺序是 O(n) 还是 O(mxn),但对于那些是 O (lg(n)) 或 O(C(power n)) 我认为我在那里遗漏了一些东西...... 那么,在快速忽略算法的情况下进行简单估计的技巧和窍门是什么?

作为我正在寻找的示例,以下是一些简单的示例(可能是错误的,但已尽力而为):

  • O(n):如果有一个从 1 到 n 的简单循环(或其中几个,但不是嵌套的。
  • O(mxn):另一个嵌套循环,其中限制为 m 和 n。

提前致谢。

【问题讨论】:

    标签: algorithm performance big-o performance-estimation


    【解决方案1】:

    我不喜欢回答自己的问题,但我今天发现了这个并提醒我这个 SO 问题。

    http://bigocheatsheet.com/

    【讨论】:

      【解决方案2】:

      通常会出现像 O(logN) 这样的数据,因为数据大小为 N,但它是有组织的,例如在树的深度为 logN 的树中。如果典型的搜索涉及从根到叶(在更坏的情况下),那么很容易看出算法将是 O(logN)。

      没有硬性规定 - 您只需查看每个案例,找出最坏的情况,然后计算成本。

      【讨论】:

        【解决方案3】:

        算法的渐近复杂度在实践中很重要,以下是我在查看自己的或其他人的代码时使用的一些经验法则。通常实际的计算机算法是许多变量和非平凡数据结构的函数,但让我们假设(仅用于说明)我们的算法 f 基本上以单个整数 X 作为其参数,并且我们希望找到 f 的渐近复杂度X. 假设 f(0) 是微不足道的。那么一般来说:

        • 从 0 到 X 的每个嵌套循环都会为 X 添加一个指数,因此两个循环(一个嵌套在另一个循环中)给出 X**2(二次)。
        • 如果 f(X) 以尾递归方式调用 f(X-1),它通常对应于迭代,即单个外循环 (O(X))。
        • 我见过作者打算迭代的例程,但其中既有从 0..X 的迭代,又有到 X-1 的尾递归;这些导致二次行为 (O(X**2))
        • 如果 f(X) 调用 f(X-1) 两次或更多次,它会产生一个指数算法,并且您会从中得到 O(2**X)。
        • 如果 f(X) 调用 f(X/2) 两次,则它在复杂性方面对应于单次迭代(它是一种分而治之的算法)。 (根据细节,它会导致 O(X log X) 或 O(X),但我意识到无论如何我认为它是一次迭代。)
        • 如果 f 使用任何已正确实现的有序数据结构(有序集、优先级堆等),并且算法在数据结构中添加了大约 X 个对象,则操作为 O(log X)。因此,如果在一个循环中发生恒定数量的数据结构操作,例如,您会得到 O(X * log X)。
        • 如果有序数据结构未正确实现,则单个操作可能会得到 O(X) 而不是 O(log X)。

        一些特殊情况:

        • 通过附加到字符串或内存区域来增长字符串或内存区域的算法在许多语言中会产生 O(X**2) 开销,例如

          for (i = 0; i < X; i++) { s += "foo"; } // quadratic
          
        • 这个典型的嵌套循环也有 X**2 开销:

          for (i = 0; i < X; i++) { for (j = 0; j < i; j++) { ... } } // quadratic
          
        • 像 std::set 和 std::map 这样的 C++ STL 容器几乎对所有操作都有 O(log X) 开销

        • strlen(s) 和其他类似的计数算法在返回 X 时会产生 O(X) 开销
        • memcpy 等导致 O(X)
        • 存在复杂性方面的危险操作,例如通过相等比较从链表中删除元素,这会产生 O(X) 或更糟
        • 使用基于模板的容器时,请确保比较、排序等操作符快速且没有隐藏的复杂因素
        • 如果您使用引用计数,如果您将最后一个引用删除到长度为 X 的引用链接列表,则删除引用可能是最坏情况的 O(X) 操作
        • 如果复制对象的例程不平凡并且例如更新全局对象集

        只要我的 2 美分!希望这会有所帮助!

        【讨论】:

          【解决方案4】:

          O(lg(n)):如果您的问题在算法的每一步都缩小了 n 的某个比例(通常是 n/2),并且每一步都做了大量的工作。二分搜索就是一个很好的例子,因为每一步都通过做恒定的工作量(计算中点并进行一次比较)将问题规模减半。

          请注意,n 位于该比例的顶部。这与您的问题大小在每一步减少 1/n 不同。 :)

          【讨论】:

          • 您错过了计算的一个重要部分。您将有 O(log N) 步。但只有当每一步都需要 O(1) 的工作量时,总工作量才会是 O(log N)。如果每个步骤需要 O(N) 工作(如快速排序),则总工作为 O(N) * O(log N) = O(N log N)
          【解决方案5】:

          Big O Notation 上的Wikipedia article 有一张漂亮的orders of common functions 图表。

          【讨论】:

            【解决方案6】:

            如果您正在寻找一种快速方法来估计算法的运行时间,那么其他答案都很好。如果您想要更详细的答案,我建议您查看“Master theorem”。在German article 中,有一张不错的表格。

            编辑: John D. Cook 对主定理做了一个很好的回顾,请参阅他的答案链接。

            【讨论】:

              【解决方案7】:

              这里有一篇博文可能会有所帮助:

              The cost of breaking things down and putting them back together

              那篇文章解释了处理大 O 订单的“主定理”。

              【讨论】:

              • 对主定理的精彩回顾! :)
              • 很棒的帖子...希望我在为我的研究生算法课程摸索google.com/books?id=NLngYyWFl_YC 的第 4 章时知道它:/ lol
              【解决方案8】:

              递归、分而治之的算法通常是 O(logN)。在分治法上循环的算法将是 O(NlogN)。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-05-24
                • 2014-07-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多