【问题标题】:which sorting algorithm to use where? [closed]在哪里使用哪种排序算法? [关闭]
【发布时间】:2012-12-15 06:39:37
【问题描述】:

有多种可用的排序算法。时间复杂度为 O(n^2) 的排序算法可能更适合 O(nlogn),因为它是就地的或稳定的。例如:

  • 对于有些排序的东西,插入排序很好。
  • 对接近排序的数组应用快速排序是愚蠢的。
  • 堆排序在 O(nlogn) 的情况下很好,但不稳定。
  • 合并排序不能用于嵌入式系统,因为在最坏的情况下它需要 O(n) 的空间复杂度。

我想知道哪种排序算法适合什么条件。

  • 哪种排序算法最适合按字母顺序对名称进行排序?
  • 哪种排序算法最适合排序较少的整数?
  • 哪种排序算法最适合排序较少但范围可能较大的整数 (98767 - 6734784)?
  • 哪种排序算法最适合对数十亿个整数进行排序?
  • 哪种排序算法最适合在空间和时间都受到限制的嵌入式系统或实时系统中进行排序?

请建议这些/其他情况、书籍或网站进行此类比较。

【问题讨论】:

  • 我找到了印地语文本的自动翻译:Bren Rahiman to see, is not short Dari. As Ava needle work, said Tlwari storm. 正确的翻译是什么?
  • 对此没有简单的答案,而且在大多数情况下,不管怎样,关心哪个是“最好的”是过度的(过早的优化)——你只需使用标准库中的排序为你的语言。但是,在对整数进行排序时,基数排序是值得了解的。
  • @AndersonGreen 这意味着“不要低估小事而不是大事。可能是剑比剑大,但它做不到针能做的事。针也同样重要”。

标签: algorithm sorting quicksort mergesort stable-sort


【解决方案1】:

好吧,没有灵丹妙药 - 但这里有一些经验法则:

  1. 当元素的范围(假设为U)与元素数量(U<<n)相比相对较小时,基数排序/计数排序通常很好(可能适合您的情况2,4)
  2. 插入排序适用于小型(例如n<30)列表,甚至比O(nlogn) 算法更快(根据经验)。事实上,你可以通过在n<30 时切换到插入排序来优化O(nlogn) 自顶向下算法
  3. 基数排序的变体也可能是按字母顺序排序字符串的不错选择,因为它是O(|S|*n),而基于比较的正常算法是O(|S|*nlogn) [其中|S| 是字符串的长度]。 (适合您的情况 1)
  4. 如果排序后的输入非常大,太大而无法合并,则可以使用外部排序 - 这是一种变体或合并排序,它可以最大限度地减少磁盘读取/写入的次数并确保这些顺序完成 - 因为它极大地提高了性能。 (可能适合案例 4)
  5. 用于一般大小写排序、快速排序和 timsort(用于 java) 提供良好的性能。

【讨论】:

  • +1 - 我唯一的抱怨是 AFAIK“外部排序”并不意味着任何特定的排序算法。任何适用于外部存储的排序都是外部排序。归并排序绝对是一种很好的外部排序算法,但不一定是唯一的。特别是,忽略缓存的漏斗排序应该适用于硬盘驱动器上的数据,前提是您能找到足够了解它的人来实现它。
【解决方案2】:

在最坏的情况下不能在嵌入式系统中使用合并排序 需要 O(n) 的空间复杂度。

您可能对 C++ 中的 stable_sort 函数感兴趣。它尝试为常规合并排序分配额外空间,但如果失败,它会执行时间复杂度较低的就地稳定合并排序(n * ((log n)^2) 而不是n * (log n))。如果您可以阅读 C++,则可以查看您最喜欢的标准库中的实现,否则我希望您可以在与语言无关的术语中找到解释的细节。

有大量关于就地稳定排序(尤其是就地合并)的学术文献。

所以在 C++ 中,经验法则很简单,“如果需要稳定的排序,请使用 std::stable_sort,否则使用 std::sort”。 Python 再次让它变得更容易,经验法则是“使用sorted”。

一般来说,你会发现很多语言都有相当聪明的内置排序算法,而且你大部分时间都可以使用它们。您很少需要实现自己的才能击败标准库。如果您确实需要实现自己的,那么没有什么可以替代教科书,用尽可能多的技巧实现一些算法,并针对特定对它们进行相互测试> 您担心需要击败库函数的情况。

您在回答这个问题时可能希望得到的大多数“显而易见”的建议已经被纳入一种或多种常见编程语言的内置排序功能中。但要回答您的具体问题:

哪种排序算法最适合按字母顺序对名称进行排序?

基数排序可能会优于标准比较排序,如 C++ sort,但如果您对名称使用“正确”的排序规则,这可能是不可能的。例如,“McAlister”过去的字母顺序与“MacAlister”相同,“St. John”的字母顺序与“Saint John”相同。但后来程序员出现了,他们只想按 ASCII 值排序,而不是编写许多特殊规则,因此大多数计算机系统不再使用这些规则。我发现星期五下午是这种功能的好时机;-) 如果您对“规范化”名称的字母而不是实际名称进行排序,您仍然可以使用基数排序。

英语以外的其他语言的“正确”排序规则也很有趣。例如在德语中,“Grüber”类似于“Grueber”,因此出现在“Gruber”之后但在“Gruhn”之前。在英语中,“Llewellyn”这个名字出现在“Lewis”之后,但我相信威尔士语(使用完全相同的字母表但不同的传统排序规则)它在前面。

因此,谈论优化字符串排序比实际操作要容易。 “正确”排序字符串需要能够插入特定于语言环境的排序规则,如果您放弃比较排序,则可能需要重新编写所有排序代码。

哪种排序算法最适合排序较少的整数?

对于少量的小值,可能是计数排序,但是当数据变得足够小(20-30 个元素)时切换到插入排序的 Introsort 非常好。当数据不是随机的时,Timsort 特别好。

哪种排序算法最适合排序较少但范围可能较大(98767 - 6734784)的整数?

大范围排除了计数排序,因此对于少量范围广泛的整数,Introsort/Timsort。

哪种排序算法最适合对数十亿个整数进行排序?

如果您所说的“十亿”是指“太多而无法放入内存”,那么这会稍微改变游戏规则。可能您想将数据分成适合内存的块,Intro/Tim 对每个块进行排序,然后进行外部合并。如果您在 64 位机器上对 32 位整数进行排序,则可以考虑计数排序。

哪种排序算法最适合在空间和时间都受到约束的嵌入式系统或实时系统中进行排序?

可能是自我介绍。

对于有些排序的东西,插入排序很好。

没错,Timsort 利用了同样的情况。

对几乎排序的数组应用快速排序是愚蠢的。

错误。没有人使用最初由 Hoare 发布的普通 QuickSort,您可以做出更好的枢轴选择,使杀手案例比“排序数据”更不明显。为了彻底处理不良情况,有 Introsort。

堆排序在 O(nlogn) 时很好,但不稳定。

没错,但 Introsort 更好(而且也不稳定)。

合并排序不能用于嵌入式系统,因为在最坏的情况下它需要 O(n) 的空间复杂度。

通过像std::stable_sort 那样允许稍微慢一些的就地合并来处理这个问题。

【讨论】:

  • 给霍尔一些功劳。他的论文可从 oxfordjournals.org 合法获得,包括随机选择枢轴以及例如三。
  • 合并排序可以就地完成。 Knuth 在 The Art of Computer Programming 第 5.2.4 节中概述了 Kronrod 的解决方案。在 CACM Vol 31 (1988), pp 348-352 的 Huang 和 Langston 的论文中有另一种算法的描述。
  • @user515430: 对,“plain QuickSort”实际上是一个稻草人,一种没有人使用的算法,但它引入了使用微不足道的枢轴选择进行分区排序的想法。但由于某种原因,人们仍然认为 QuickSort 存在已排序输入的问题。我不怪霍尔。正如您所说,他的论文清楚地表明枢轴的选择很重要但并不明显,并且他没有在那里规定任何特定的选择。我不知道真正选择第一个元素作为枢轴的想法是什么时候引入的:可能是在他的示例代码中,也可能是其他人。
  • 有关就地合并的更多信息,请参阅我的旧答案 (stackoverflow.com/questions/2571049/…)。 AFAIK 达到理论复杂性界限的技术在实践中很慢,所以它们不是很好的通用选择。正如我最初所说,就地合并是可能的,但速度较慢。
  • 即使合并排序只是简单地完成,而不是就地完成,它也需要 O(n) 额外空间,但根本不需要 O(n) RAM - 甚至对于原始数据。这就是它在古代流行的原因之一 - 几乎没有 RAM 但具有多个磁带驱动器的机器可以通过运行磁带来运行合并排序,但需要多次运行。原则上,您现在可以使用相同的技巧对 TB 级数据集进行排序,只要您至少有两个物理硬盘驱动器,硬盘磁头抖动就会最少。
猜你喜欢
  • 2011-11-02
  • 1970-01-01
  • 2011-11-14
  • 1970-01-01
  • 2010-09-17
  • 2013-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多