【问题标题】:Using red black trees for sorting使用红黑树进行排序
【发布时间】:2011-03-17 06:42:57
【问题描述】:

red-black tree 上插入的最坏情况运行时间是O(lg n),如果我在树上执行in-order walk,我基本上会访问每个节点,因此打印排序集合的总最坏情况运行时间将是 O(n lg n)

我很好奇,为什么red-black trees 不是优先于quick sort(其平均案例运行时间为O(n lg n))的排序。

我看到这可能是因为 red-black trees 没有就地排序,但我不确定,所以也许有人可以提供帮助。

【问题讨论】:

  • 一般来说,搜索首选树。排序:排序一次。树:多次搜索。
  • 按顺序遍历树需要 O(1) 时间每个访问的节点,所以它应该在 O(n) 而不是 O(n lg n) 中运行。
  • 按顺序走需要堆栈空间,否则非 O(1) get_first 和后继。

标签: algorithm sorting quicksort red-black-tree


【解决方案1】:

在很多情况下,红背树对排序并不坏。我的测试表明,与自然归并排序相比,红黑树在以下方面表现出色:

树更适合 Dups: 所有需要消除重复的测试,树算法更好。这并不令人惊讶,因为树从一开始就可以保持很小,因此为内联数组排序设计的算法可能会在更长的时间内绕过更大的段。

树更适合随机: 所有使用随机树算法的测试都更好。这也不足为奇,因为在树中元素之间的距离更短,并且不需要移动。因此,重复插入到树中可能比对数组进行排序需要更少的努力。

所以我们得到的印象是自然归并排序只在升序和降序的特殊情况下表现出色。对于快速排序,这甚至不能说。

测试用例的要点here

P.S.:应该注意的是,使用树进行排序并非易事。一个人不仅必须提供一个插入例程,而且还必须提供一个可以将树线性化回数组的例程。我们目前正在使用一个 get_last 和一个前置例程,它不需要堆栈。但是这些例程不是 O(1),因为它们包含循环。

【讨论】:

    【解决方案2】:

    您好,我认为解释所有排序例程之间差异的最佳方法是。 (我的回答是针对那些对在实践中快速排序比其他排序算法更快的人感到困惑的人)。

    “认为你在一台非常慢的计算机上运行”。

    1. 首先比较操作需要 1 小时。
    2. 一个班次操作需要2个小时。

    “我使用小时只是为了让人们了解时间的重要性”。

    现在从所有排序操作中,快速排序的比较和元素交换都非常少。

    由于这个主要原因,快速排序更快。

    【讨论】:

      【解决方案3】:

      了解哪种排序算法性能更好实际上取决于您的数据和情况。

      如果你说的是一般/实用的术语,

      快速排序(您随机选择枢轴/只选择一个固定的枢轴,使最坏的情况 Omega(n^2))可能比红黑树更好,因为(不一定按重要性顺序)

      • 快速排序已就地。使您的内存占用低。假设这个快速排序例程是处理大量数据的程序的一部分。如果您一直使用大量内存,您的操作系统可能会开始交换您的进程内存并破坏您的性能。

      • 快速排序内存访问已本地化。这与缓存/交换配合得很好。

      • 快速排序可以轻松实现并行化(现在可能更相关)。

      • 如果您尝试通过使用数组来优化二叉树排序(使用不平衡的二叉树),您最终会执行类似快速排序的操作!

      • 红黑树有内存开销。您可能必须多次分配节点,您对树的内存需求是使用数组的两倍/三倍。

      • 排序后,假设您想要第 1045 个(比如说)元素,您需要在树中维护订单统计信息(因此需要额外的内存成本),并且您将有 O(logn) 访问时间!

      • 红黑树有开销只是为了访问下一个元素(指针查找)

      • 红黑树不能很好地与缓存配合使用,指针访问可能会导致更多的交换。

      • 红黑树中的旋转会增加 O(nlogn) 中的常数因子。

      • 也许是最重要的原因(但如果您有可用的 lib 等则无效),快速排序非常易于理解和实现。连小学生都能看懂!

      我会说你尝试测量这两种实现,看看会发生什么!

      另外,Bob Sedgewick 写了一篇关于快速排序的论文!可能值得一读。

      【讨论】:

      • 塞奇威克还参与了红黑树和左倾红黑树的发明。
      • 您可以实现红黑树,其中节点只分配一次,旋转可以通过纯指针混合来完成。但当然,节点需要比数组单元更多的空间。
      【解决方案4】:

      通常,O(nlgn) 算法的表示可以扩展为 A*nlgn + B,其中 A 和 B 是常数。有许多算法证明表明快速排序的系数小于其他算法的系数。这是最好的情况(快速排序对已排序的数据执行得非常糟糕)。

      【讨论】:

        【解决方案5】:

        Big-O 时间复杂度度量通常不考虑标量因素,例如,O(2n) 和 O(4n) 通常只是简化为 O(n)。时间复杂度分析基于算法级别的操作步骤,而不是严格的编程级别,即不考虑源代码或本地机器指令。

        快速排序通常比基于树的排序更快,因为 (1) 这些方法具有相同的算法平均时间复杂度,以及 (2) 使用简单数组时,查找和交换操作需要的程序命令和数据访问比使用 red-黑树,即使树使用底层的基于数组的实现。维护红黑树约束需要额外的操作步骤、数据字段值存储/访​​问(节点颜色)等,而不是快速排序的简单数组分区交换步骤。

        最终结果是红黑树的标量系数比快速排序更高,而标准 O(n log n) 平均时间复杂度分析结果掩盖了这一点。

        Quicksort article on Wikipedia 中简要讨论了与机器架构相关的其他一些实际考虑因素

        【讨论】:

          【解决方案6】:

          有很多排序算法是最坏的情况O(n log n) - 例如,归并排序。首选快速排序的原因是因为它在实践中更快,尽管它在算法上可能不如其他一些算法。

          通常,内置排序根据 n 的值使用各种方法的组合。

          【讨论】:

          • 但是合并排序不能就地排序,在实践中快速排序如何更快? (我一直不明白,虽然每次我问这个问题时都会发现它扔给我)
          • 所有排序算法都涉及到它们自己的隐藏常量因素,虽然您可能会通过一些搜索找到关于为什么会出现这种情况的论文,但试图确定理论上哪个实际上执行得更快不是简单。实际上就是这个意思——如果你比较真实数据上的排序算法,你会发现快速排序不可避免地具有更快的运行时间。
          • 快速排序并不总是更快。如果你不断增加元素的数量,O(n log n) 算法将不可避免地在某个时候击败 O(n^2) 算法。但是对于较小的 n,常数因子的影响要大得多,而 O(n^2) 算法实际上可能是更快的解决方案。例如考虑 10000*n 和 100*n^2。起初,100*n^2 会产生较小的值,但在 n=100 时,线性函数会赶上并为所有进一步的 n 产生较小的值。快速排序的效果是相同的,并且对于大多数“实用”n 它更快。
          • @Gnafoo - 您的示例中的分析是正确的,但在这种情况下,正在比较具有 same O(n log n) 平均时间复杂度的两种算法。在大多数实际应用中,平均时间复杂度通常是最重要的;最坏情况下的时间复杂度通常只与具有严格执行时间限制要求的少数场景相关。
          猜你喜欢
          • 2014-08-19
          • 2012-09-16
          • 2017-03-16
          • 2010-09-06
          • 2015-11-17
          • 1970-01-01
          • 2011-04-23
          • 2013-02-28
          • 2011-02-04
          相关资源
          最近更新 更多