为什么实践中quicksort比其他排序算法更好?


31

这是Janoma对cs.SE的一个问题转贴。对他或CS SE的全部荣誉和破坏。

在标准算法课程中,我们学会了快速排序平均为O(n log n),在最坏情况下为O(n²)。同时,还研究了其他排序算法,它们在最坏的情况下为O(n log n)(例如mergesortheapsort),在最坏的情况下甚至是线性时间(例如bubbleort),但还有一些额外的内存需求。

快速浏览一下更多的运行时间后,自然可以说quicksort 应该不如其他高效。

另外,考虑到学生在基础编程课程中学习到,递归通常并不太好,因为它会占用过多的内存,等等。因此(尽管这不是一个真正的论点),但这样的想法是快速排序可能不是真的很好,因为它是一种递归算法。

那么,为什么在实践中快速排序优于其他排序算法?它与真实数据的结构有关吗?它与计算机中内存的工作方式有关吗?我知道有些记忆要比其他记忆快,但是我不知道这是否是这种违反直觉的表现的真正原因(与理论估计相比)。


3
Quicksort信誉可从不存在缓存的时间开始。
AProgrammer

9
“为什么实践中快速排序优于其他排序算法?” 确定是真的吗?使用此语句向我们展示您引用的实际实现,社区将告诉您为什么该特定实现会按其行为方式运行。其他所有内容都会导致对不存在的程序的疯狂猜测。
布朗

1
@DocBrown:在许多库中选择了许多Quicksort(或它的变体)实现,可以说是因为它们表现最佳(我希望是这样)。因此,可能存在使Quicksort快速实现算法,与实现无关。
拉斐尔2012年

1
出于完整性的考虑,必须有人说这一点,因此,我将:Quicksort((通常)不稳定)。因此,您可能不想使用它。同样,由于这个原因,即使您想要的是默认排序也不是Quicksort。
拉尔夫·查平(RalphChapin)

1
@Raphael:通常所谓的快速排序实际上是一些变化,例如介绍性排序(在C ++标准库中使用过的afaik),而不是纯粹的快速排序。
乔治

Answers:


21

我不会同意quicksort在实践中比其他排序算法要好。

在大多数情况下,Timsort是mergesort / insertion排序之间的混合体,它利用了这样一个事实,即您排序的数据通常始于几乎排序或反向排序的数据。

最简单的快速排序(无随机枢轴)将这种潜在的常见情况视为O(N ^ 2)(通过随机枢轴减少为O(N lg N)),而TimSort可以在O(N)中处理这些情况。

根据C#中的这些基准,将内置的快速排序与TimSort 进行比较,在大多数排序情况下,Timsort明显更快,而在随机数据情况下,Timsort则稍快一些,如果比较功能特别慢,TimSort会变得更好。我没有再重复这些基准测试,如果quicksort在随机数据的某种组合方面稍胜于TimSort,或者C#的内置排序(基于quicksort)中有一些古怪的东西使它变慢,我也不会感到惊讶。但是,在对数据进行部分排序时,TimSort具有明显的优势;在对数据进行不部分排序时,TimSort在速度上大致相当于快速排序。

与快速排序不同,TimSort还具有稳定排序的优点。在常规(快速)实现中,TimSort的唯一缺点是使用O(N)与O(lg N)内存。


18

快速排序被认为是更快的,因为该系数小于任何其他已知算法。没有任何理由或证据,只是没有找到系数较小的算法。确实,其他算法也具有O(n log n)时间,但在现实世界中,系数也很重要。

请注意,由于数学函数的性质,对于小数据插入排序(被认为是O(n 2)的排序)更快。这取决于因机器而异的特定系数。(最后,只有程序集才真正运行。)所以我认为有时候快速排序和插入排序的混合是最快的。


7
+对。教师需要更加了解(我曾是一名教师)以下事实:恒定因素可能会变化一个数量级。因此,无论big-O为何,性能调优的技巧确实很重要。问题在于,他们继续教gprof,只是因为他们必须超越课程表中的要点,这是错误的方法180度。
迈克·邓拉维

2
“没有理由或理由”:肯定有。如果您深入研究,就会找到原因。
吉尔斯(Gillles)'所以

2
@B七:简化很多...对于O(n log n)排序算法,排序循环有(n log n)次迭代以对n个项目进行排序。该系数是循环的每个周期花费的时间。当n非常大(至少数千个)时,即使系数很大,系数也不会像O()那样重要。但是,当n小时,系数很重要-如果仅排序10个项目,系数就可能是最重要的。
马特·加拉格尔

4
@MikeDunlavey-一个很好的例子是,建造金字塔为O(n),而对它们的照片进行排序为O(n ln n),但这更快!
马丁·贝克特

2
有保证的O(n log n)算法,例如堆排序和合并排序,因此在渐近最坏情况下,快速排序的速度甚至不及最佳速度。但是在现实世界中,某些快速排序变体效果非常好。但是,说“系数更小”就像说“更快,因为它更快”。为什么恒定因子这么小?一个关键原因是因为quicksort在本地方面非常好-它很好地利用了缓存。归并具有良好的地方太多,但它是非常难的,地方做。
2012年

16

Quicksort不能胜过所有其他排序算法。例如,对于合理数量的数据,自下而上的堆排序(Wegener 2002)优于快速排序,这也是一种就地算法。它也易于实现(至少不比某些优化的quicksort变体难)。

它只是不太知名,您在很多教科书中都找不到它,这也许可以解释为什么它不如quicksort受欢迎。


+1:我已经进行了一些测试,对于大型数组(> 100000个元素),合并排序绝对比快速排序好。堆排序比合并排序稍差一些(但是合并排序需要更多的内存)。我认为人们所谓的快速排序通常是一种称为“入门排序”的变体:快速递归在递归深度超过特定限制时回落到堆排序。
乔治

@Giorgio:可以通过某些方式对quicksort进行修改以改进它,例如,请参见此处:algs4.cs.princeton.edu/23quicksort您是否尝试了这种改进?
布朗

有趣的是,您可以引用某本书\站点的内容以进一步了解它吗?(最好是一本书)
拉姆齐·卡希尔

@马丁:你是说从下而上的堆排序?好吧,我在上面给出了参考。如果您想要免费的资源,则德语维基百科上有一篇有关它的文章(de.wikipedia.org/wiki/BottomUp-Heapsort)。即使您不会说德语,我想您仍然可以阅读C99示例。
布朗

7

您不应该只关注最坏情况,也不要关注时间复杂度。它比平均更重要,而不是最坏,而且是关于时间空间。

快速排序:

  • 具有平均 Θ的时间复杂度(Ñ登录Ñ);
  • 可以用Θ(log n)的空间复杂度实现;

还应考虑到,大的O表示法不考虑任何常量,但是在实践中,如果算法快几倍,则确实会有所作为。Θ(n log n)表示该算法在K  n  log(n)中执行,其中K为常数。Quicksort是K 最低 的比较排序算法。


1
@吉尔斯:它的K低,因为它是一种简单的算法。
vartec

5
WTF?这没有任何意义。算法的简单性与其运行速度无关。选择排序比快速排序更简单,但这并不能使其更快。
吉尔斯(Gilles)'“ SO-不要邪恶”

1
@Gilles:在任何情况下(最差,最差和最差),选择排序为O(n ^ 2)。因此,它有多简单都没关系。对于一般情况,Quicksort是O(n log n),在所有O(n log n)平均的算法中,它是最简单的。
vartec

1
@吉尔斯:在其他条件相同的情况下,简单性确实有助于提高性能。假设您正在比较两种算法,每种算法分别对其各自的内部循环进行(K n log n)次迭代:该算法每个循环需要做的工作更少,因此具有性能优势。
2012年

1
@comingstorm:措辞像您的陈述是重言式,但与“简单性”无关。例如,存在Quicksort的更复杂的变体(区分大小写!),从而导致运行时间更短(在理论和实践上)。
拉斐尔2012年

5

Quicksort通常是一个不错的选择,因为它相当快,而且相当快且易于实施。

如果您真的想非常快速地对大量数据进行排序,那么最好对MergeSort进行一些修改。可以利用外部存储,可以使用多个线程,甚至可以使用进程,但是它们对于代码来说并不简单。


1

算法的实际性能取决于平台,以及语言,编译器,程序员对实现细节的关注,特定的优化工作等。因此,快速排序的“常量因素优势”不是很明确-它是基于当前可用工具的主观判断,是实际进行比较性能研究的人对“等效实现工作量”的粗略估计。 。

就是说,我相信quicksort(对于随机输入)表现良好,因为它很简单,并且其递归结构相对于缓存友好。另一方面,由于最坏的情况很容易触发,因此快速排序的任何实际使用都需要比其教科书描述所表明的更为复杂:因此,需要使用诸如introsort的修改版本。

随着时间的推移,随着主要平台的变化,不同的算法可能会获得或失去其(不确定的)相对优势。关于相对性能的传统观点可能远远落后于这种转变,因此,如果您真的不确定哪种算法最适合您的应用程序,则应同时实现并测试它们。


我想其他人所称的“较小常数”是形式分析中的那个,即比较或交换的次数。定义非常明确,但尚不清楚如何将其转换为运行时。实际上,一位同事目前对此进行了一些研究。
拉斐尔2012年

我的印象是,这与普遍表现有关,但我都不愿指望这两者。不过,您是对的:如果您的比较特别昂贵,则可以查找预期的比较数量...
即将到来的风暴

1
出于您陈述的原因,在总体情况下,谈论整体性能(按时)并没有意义,因为其中会包含过多的细节。仅计算选择操作的原因并不是它们昂贵,而是它们“最常出现” ”在Landau表示法(Big-Oh)的意义上进行计算,因此对它们进行计数会给您带来大致的渐近症状。一旦考虑了常量和/或运行时,这种策略就没有那么有趣了。
拉斐尔2012年

QuickSort的一个良好实现将进行编译,以使您的枢轴值在需要时一直保留在CPU寄存器中。这通常足以在理论上比Big-O更快地胜过同类。
丹·里昂斯

就比较次数和它们进行的互换次数而言,不同的排序算法具有不同的特性。@DanLyons指出,库中的典型排序是通过用户提供的函数执行其比较的,并且在许多函数调用中将值保存在寄存器中非常棘手。
尖尖的
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.