为什么Quicksort比mergesort更好?


354

采访中有人问我这个问题。它们都是O(nlogn),但是大多数人都使用Quicksort而不是Mergesort。这是为什么?


91
这不是一个很好的面试问题。现实世界中的数据不会被打乱:它通常包含一个智能排序可以使用的很多顺序,尽管两种算法都不能自动执行此操作,但修改合并排序比执行快速排序更容易。GNU libc的qsort,Python的list.sortArray.prototype.sortFirefox的JavaScript中的所有内容都是合并的。(GNU STL sort改用Introsort,但这可能是因为在C ++中,交换可能胜过复制。)
Jason Orendorff

3
@Jason Orendorff:为什么"easier to hack a mergesort to do it than a quicksort"?您可以引用任何具体示例吗?
Lazer

16
@eSKay合并排序是通过将初始数据分组到已排序的子数组开始的。如果数组最初包含一些已经排序的区域,则可以在开始之前检测它们是否存在,从而节省大量时间。您可以在O(n)时间内完成。有关特定示例,请参见我提到的三个项目的源代码!最好的示例可能是Python的Timsort,在这里进行了详细描述:svn.python.org/view/python/trunk/Objects/…并在svn.python.org/view/python/trunk/Objects/…中实现。
杰森·奥伦多夫

4
@JasonOrendorff:不确定我是否同意您的观点,即可以更轻松地修改mergesort以利用已经排序的部分。可以对quicksort的分区步骤进行微不足道的修改,以随后检查是否对两个结果分区都进行了排序,如果是,则停止递归。这可能会使比较次数加倍,但不会改变该步骤的O(n)时间复杂度。
j_random_hacker 2012年

3
@j_random_hacker:是的,这就是我的意思。但是请考虑:{10、2、3、4、5、6、7、8、1、9}尽管已经几乎完全排序,但在分区之前或之后都找不到分区进行检查。并且该分区将在后续调用对其进行检查之前将其拧紧。同时,合并排序会在分割步骤移动之前检查分割步骤中的排序序列,而智能排序将在分割步骤中专门查找类似的运行(请参阅:Tim Sort)
Mooing Duck 2014年

Answers:


275

Quicksort具有O(n 2)最坏情况运行时和O(n log n)平均情况运行时。但是,在许多情况下合并排序会更好,因为许多因素会影响算法的运行时间,并且将它们放在一起时,快速排序会胜出。

特别地,经常引用的排序算法的运行时是指执行比较或对数据进行排序所需的交换次数。这确实是一个很好的性能衡量标准,尤其是因为它独立于底层硬件设计。但是,其他因素(例如引用的局部性(即,我们是否读取了很多可能在缓存中的元素?))在当前硬件上也起着重要作用。特别是Quicksort,几乎不需要额外的空间,并且具有良好的缓存局部性,因此在许多情况下,它比合并排序要快。

此外,通过适当选择枢轴(例如随机选择),几乎完全可以避免quicksort的最坏情况下的运行时间O(n 2)。

实际上,quicksort的许多现代实现(特别是libstdc ++的std::sort)实际上是introsort,其理论上最差的情况是O(n log n),与归并排序相同。它通过限制递归深度,并在超过log n时切换到其他算法(heapsort)来实现此目的。


4
维基百科文章指出,它切换到堆排序,而不是合并排序...仅供参考。
西弗

3
@Sev:……原始报纸也是如此。感谢您指出错误。–并不是很重要,因为它们的渐近运行时间是相同的。
康拉德·鲁道夫

110
为什么选择这个作为正确答案?它所解释的只是如何修补快速排序问题。它仍然没有告诉我们为什么快速排序比其他更多?答案是否为“快速排序比其他排序更多使用,因为经过一定深度后您可以切换到堆排序”?..为什么不首先使用heapsort?..只是想了解...
codeObserver 2011年

16
@ p1好问题。真正的答案是,就平均数据而言,平均而言,快速排序要比合并排序(和堆排序)要快,即使快速排序的最坏情况比合并排序要慢,也可以很轻松地缓解这种最坏情况(因此我的回答)。
康拉德·鲁道夫

4
Quicksort在内存方面也更好。
2014年

287

正如许多人所指出的,Quicksort的平均案例性能比mergesort更快。 但这只有在假设您有恒定的时间按需访问任何内存时才是正确的。

在RAM中,此假设通常不太差(由于高速缓存,它并不总是正确的,但也不太糟)。但是,如果您的数据结构足够大,可以存储在磁盘上,那么快速排序就会您的平均磁盘每秒执行200次随机寻道的速度杀死。但是,同一张磁盘没有顺序顺序读取或写入每秒兆字节数据的麻烦。这正是mergesort所做的。

因此,如果必须在磁盘上对数据进行排序,那么您真的很想在mergesort上使用一些变体。(通常,您先对子列表进行快速排序,然后在某个大小阈值以上将它们合并在一起。)

此外,如果您必须对如此大小的数据集执行任何操作,请认真考虑如何避免寻找磁盘。例如,这就是为什么这样的建议,即在数据库中进行大量数据加载之前先删除索引,然后再重建索引,这是标准建议。在加载期间保持索引意味着不断寻找磁盘。相反,如果删除索引,则数据库可以通过以下方式重建索引:首先对要处理的信息进行排序(当然要使用mergesort!),然后将其加载到该索引的BTREE数据结构中。(BTREE本质上是保持顺序的,因此您可以从排序的数据集中加载一个,而很少有磁盘寻道。)

在很多情况下,了解如何避免磁盘寻道使我使数据处理作业花费数小时而不是数天或数周。


1
非常好,没有考虑访问数据结构的假设。深刻的见解:)
chutsu 2014年

2
您能否解释“搜索磁盘”的含义,这意味着在数据存储在磁盘上时搜索某个单一值吗?
James Wierzba 2015年

8
@JamesWierzba我认为他的意思是“寻求磁盘上的某个位置”。在旋转磁盘设备上“寻找”意味着拿起读取头并将其移动到新的绝对地址,这是众所周知的缓慢操作。当您按存储顺序访问数据时,磁盘硬件不必寻找,它只是高速扫过,依次读取项目。
nclark

1
可以再解释一下吗?我是这样看的:Quicksort:如果我们要使用随机数据透视,则调用堆栈具有以随机方式分区的数组片段。这需要随机访问。但是,对于堆栈中的每个调用,左指针和右指针都按顺序移动。我假设这些将保留在缓存中。交换再次是对缓存(最终写入磁盘)中信息的操作。(续下
一条

1
有助于避免昂贵的磁盘读/写开销:在对需要磁盘访问的非常大的数据进行排序时,为每次通过切换排序方向是有利的。也就是说,在循环的最高层,一旦您从0进入n,下次便从n进入0。这带来了以下优势:撤消(排序)内存(缓存)中已经可用的数据块,并且仅对一个磁盘访问进行两次攻击。我认为大多数DBMS都使用这种优化技术。
ssd18年

89

实际上,QuickSort是O(n 2)。它的平均运行时间为O(nlog(n)),但最差的运行时间为O(n 2),当您在包含很少的唯一项目的列表上运行它时,会发生这种情况。随机化为O(n)。当然,这不会改变最坏的情况,它只是防止恶意用户使您的排序花费很长时间。

QuickSort之所以受欢迎,是因为它:

  1. 就地(MergeSort要求额外的内存与要排序的元素数量成线性关系)。
  2. 有一个小的隐藏常数。

4
实际上,在最坏的情况下,有一些QuickSort的实现是O(n * log(n)),而不是O(n ^ 2)。
jfs

12
它还取决于计算机体系结构。Quicksort受益于缓存,而MergeSort则不然。
Cristian Ciupitu

4
@JF Sebastian:这些很可能是introsort的实现,而不是quicksort(introsort从quicksort开始,如果要停止成为n * log(n),则切换到heapsort)。
CesarB

44
您可以就地实现mergesort。
Marcin's

6
合并排序的实现方式仅需要O(1)额外的存储空间,但是其中大多数实现方式在性能方面都会遭受很大损失。
清晰的时间2014年

29

“但是大多数人使用Quicksort而不是Mergesort。为什么呢?”

尚未给出的心理原因之一就是Quicksort的命名更加巧妙。即良好的营销。

是的,具有三重分割的Quicksort可能是最好的通用排序算法之一,但是“ Quick”排序听起来比“ Merge”排序强大得多。


3
不回答关于哪个更好的问题。该算法的名称与确定哪个更好无关。
Nick Gallimore

18

正如其他人指出的那样,Quicksort的最坏情况是O(n ^ 2),而mergesort和heapsort保持在O(nlogn)。但是,一般情况下,所有三个都是O(nlogn);因此它们在绝大多数情况下都是可比的。

平均而言,使Quicksort更好的原因是内部循环意味着将多个值与一个值进行比较,而在另外两个值上,每次比较这两个术语都不相同。换句话说,Quicksort的读取次数是其他两种算法的一半。在现代CPU上,性能在很大程度上取决于访问时间,因此最终Quicksort成为了一个不错的首选。


9

我想补充一下到目前为止提到的三个算法(mergesort,quicksort和堆排序),只有mergesort是稳定的。也就是说,对于具有相同键的那些值,顺序不会更改。在某些情况下,这是理想的。

但是,说实话,在实际情况下,大多数人只需要良好的平均表现,而quicksort是... quick =)

所有排序算法都有其起伏。请参阅Wikipedia文章中有关排序算法的完整概述。


7

Quicksort上的Wikipedia条目中

Quicksort还与另一种递归排序算法mergesort竞争,但它具有最坏情况下Θ(nlogn)运行时间的优点。与快速排序和堆排序不同,合并排序是一种稳定的排序,可以轻松地对其进行调整,以对存储在访问速度较慢的介质(例如磁盘存储或网络连接存储)上的链表和非常大的列表进行操作。尽管可以将quicksort编写为可在链接列表上进行操作,但通常会在没有随机访问权限的情况下遭受枢轴选择不当的困扰。mergesort的主要缺点是,在数组上进行操作时,在最佳情况下需要Θ(n)辅助空间,而具有就地分区和尾递归的quicksort变体仅使用Θ(logn)空间。(请注意,在对链表进行操作时,mergesort仅需要少量恒定的辅助存储。)


7

亩! Quicksort并不是更好,它比mergesort更适合于其他类型的应用程序。

如果速度至关重要,那么Mergesort值得考虑,不能容忍最差的糟糕表现,并且可以提供额外的空间。1个

您说过他们“他们都是O(nlogn)[…]»”。错了 «在最坏的情况下,Quicksort使用大约n ^ 2/2的比较。» 1

但是,根据我的经验,最重要的属性是在使用带有命令式范式的编程语言时在排序时可以轻松使用顺序访问。

1 Sedgewick,算法


Mergesort可以就地实现,因此不需要额外的空间。例如,使用双链表:stackoverflow.com/questions/2938495/…–
lanoxx

6

Quicksort是实践中最快的排序算法,但由于存在许多病理情况,因此其性能可能与O(n2)一样差。

堆排序保证在O(n * ln(n))中运行,并且仅需要有限的附加存储。但是现实世界中有很多引用表明,堆排序平均比快速排序慢得多。


5

维基百科的解释是:

通常,快速排序在实践中比其他Θ(nlogn)算法快得多,因为它的内部循环可以在大多数体系结构上有效实现,并且在大多数实际数据中,可以进行设计选择,从而将需要二次时间的可能性降到最低。

快速排序

合并排序

我认为Mergesort所需的存储量(Ω(n))也存在一些问题,而Quicksort实施则没有。在最坏的情况下,它们的算法时间相同,但是mergesort需要更多的存储空间。


快速排序的最坏情况是O(n),合并排序O(n log n)-因此,那里的区别很大。
paul23年

1
最差的情况下,快速排序是O(n ^ 2)-无法编辑我之前的评论并输入错误
paul23 '16

@ paul23注释可以删除。同样,答案已经解决了您的观点:“在大多数实际数据中,可以做出设计选择,从而将需要二次时间的可能性降到最低”
Jim Balter

5

我想在现有的很好的答案中添加一些有关QuickSort在脱离最佳情况时的表现以及该可能性的可能性的一些数学信息,希望这有助于人们更好地理解O(n ^ 2)情况为何不真实的原因。关注QuickSort的更复杂的实现。

除随机访问问题外,还有两个主要因素可能会影响QuickSort的性能,它们都与数据透视表与正在排序的数据进行比较的方式有关。

1)数据中有少量键。具有相同值的数据集将在香草2分区QuickSort上以n ^ 2的时间排序,因为除枢轴位置以外的所有值每次都放置在一侧。现代实现通过诸如使用3分区排序的方法来解决此问题。这些方法在O(n)时间内对所有相同值的数据集执行。因此,使用这种实现方式意味着具有少量键的输入实际上可以提高性能,并且不再需要担心。

2)极差的枢轴选择会导致最坏的情况。在理想情况下,枢轴将始终保持这样的状态,即50%的数据较小而50%的数据较大,以便在每次迭代期间将输入分成两半。这给了我们n次比较,并将log-2(n)个递归时间交换为O(n * logn)时间。

非理想的数据透视选择对执行时间有多大影响?

让我们考虑这样一种情况:一致选择枢轴,使得75%的数据位于枢轴的一侧。它仍然是O(n * logn),但是现在日志的底数已更改为1 / 0.75或1.33。更改基准时性能上的关系始终是由log(2)/ log(newBase)表示的常数。在这种情况下,该常数为2.4。因此,这种枢轴选择的质量比理想的时间长2.4倍。

这种情况恶化有多快?

直到枢轴选择(总是)变得非常糟糕之前,速度不是很快:

  • 一侧50%:(理想情况)
  • 一侧75%:长度的2.4倍
  • 一侧90%:长度的6.6倍
  • 一侧95%:长度的13.5倍
  • 一侧99%:长度的69倍

当我们一侧接近100%时,执行的对数部分接近n,整个执行渐近地接近O(n ^ 2)。

在QuickSort的简单实施中,诸如排序数组(用于第一个元素枢轴)或反向排序数组(用于最后一个元素枢轴)的情况将可靠地产生最坏情况的O(n ^ 2)执行时间。此外,具有可预测枢轴选择的实现可能会受到旨在产生最坏情况执行的数据的DoS攻击。现代的实现通过多种方法来避免这种情况,例如在排序之前对数据进行随机化,选择3个随机选择的索引的中位数等。在这种混合方式下,我们有2种情况:

  • 小数据集。最坏的情况是有可能的,但是O(n ^ 2)不会造成灾难性的影响,因为n足够小,因此n ^ 2也很小。
  • 大数据集。从理论上讲,最坏的情况是可能的,但实际上却是不可能的。

我们看到糟糕表现的可能性有多大?

机会是微乎其微。让我们考虑一下5,000个值:

我们的假设实现将使用3个随机选择的索引的中位数来选择枢轴。我们将认为25%-75%范围内的枢轴为“良好”,而0%-25%或75%-100%范围内的枢轴为“不良”。如果使用3个随机索引的中值查看概率分布,则每个递归最终都有11/16的机会以良好的支点结束。让我们做出两个保守(和错误)的假设来简化数学:

  1. 好的支点总是精确地以25%/ 75%的比例分配,并在2.4 *理想情况下运行。我们永远不会获得理想的拆分或任何优于25/75的拆分。

  2. 错误的数据透视总是最坏的情况,对解决方案基本上没有任何帮助。

我们的QuickSort实现将在n = 10处停止并切换为插入排序,因此我们需要22个25%/ 75%的数据透视分区才能将5,000个值输入分解得那么远。(10 * 1.333333 ^ 22> 5000)或者,我们需要4990个最坏情况的数据透视。请记住,如果我们在任何时候积累了22个好的数据,那么排序将完成,因此最坏的情况或接近它的任何情况都将带来极大的厄运。如果我们花了88次递归才能真正实现将n = 10排序所需的22个好的枢轴,那将是4 * 2.4 *理想情况,或者是理想情况下执行时间的大约10倍。在88次递归之后,我们没有达到所需的22个良好枢纽的可能性有多大?

二项式概率分布可以回答这个问题,答案约为10 ^ -18。(n为88,k为21,p为0.6875)在单击[SORT]的1秒内,您的用户被闪电击中的可能性比看到5,000个项目的运行情况差一千倍左右比10 *理想情况。数据集越大,机会越小。以下是一些数组大小及其对应的运行时间超过10 *理想值的机会:

  • 640个项目的数组:10 ^ -13(60次尝试中需要15个好的枢轴点)
  • 5,000个项目的数组:10 ^ -18(需要88次尝试中的22个良好枢轴)
  • 40,000个数组:10 ^ -23(需要116个好的枢轴)

请记住,这是基于两个比实际情况差的保守假设。因此实际性能更好,剩余概率的平衡比不更接近理想。

最后,正如其他人所提到的,如果递归堆栈太深,甚至可以通过切换到堆排序来消除这些不太可能的情况。因此,TLDR是,对于QuickSort的良好实现而言,最坏的情况并不真正存在,因为它已被设计出来并且执行时间为O(n * logn)。


1
“现有的好答案”-哪些?我找不到他们。
吉姆·巴尔特

快速排序的任何变体是否以某种方式通知比较功能有关分区的信息,从而使它能够利用分区中所有项目的大部分键都相同的情况?
超级猫

4

为什么Quicksort很好?

  • QuickSort在最坏的情况下为N ^ 2,在平均情况下为NlogN。最坏的情况发生在对数据进行排序时。这可以通过在排序开始之前随机洗牌来缓解。
  • QuickSort不会占用合并排序所占用的额外内存。
  • 如果数据集很大并且有相同的项目,则使用3向分区可以降低Quicksort的复杂度。相同项目的数量越多,排序越好。如果所有项目都相同,则按线性时间排序。[这是大多数库中的默认实现]

Quicksort总是比Mergesort更好吗?

并不是的。

  • Mergesort稳定,但Quicksort不稳定。因此,如果需要输出的稳定性,则可以使用Mergesort。在许多实际应用中需要稳定性。
  • 如今,内存便宜。因此,如果Mergesort使用的额外内存对您的应用程序不是至关重要的,那么使用Mergesort不会有任何危害。

注意:在Java中,Arrays.sort()函数将Quicksort用于原始数据类型,将Mergesort用于对象数据类型。因为对象消耗内存开销,所以从性能的角度来看,为Mergesort添加一点开销可能不是什么问题。

参考在Coursera观看普林斯顿算法课程第3周的QuickSort视频


“这可以通过在排序开始之前进行随机混洗来缓解。”-不,那会很昂贵。而是使用随机数据透视。
吉姆·巴尔特

4

Quicksort并不比mergesort好。使用O(n ^ 2)(最罕见的情况,很少发生),快速排序可能比合并排序的O(nlogn)慢得多。Quicksort的开销较小,因此对于n较小且速度较慢的计算机,它会更好。但是今天的计算机是如此之快,以至于合并排序的额外开销可以忽略不计,而且非常慢的快速排序的风险在大多数情况下远远超过合并排序的无关紧要的开销。

此外,mergesort会按原始顺序保留具有相同键的项目,这是一个有用的属性。


2
您的第二句话说:“ ... mergesort可能比... mergesort慢得多”。第一个参考应该应该是速配。
乔纳森·莱夫勒

仅当合并算法稳定时,合并排序才稳定;这不能保证。
清晰的时间2014年

@Clearer保证如果<=用于比较而不是<,并且没有理由不这样做。
吉姆·巴尔特

@JimBalter我可以轻松地提出一个不稳定的合并算法(例如,quicksort可以充当该角色)。在许多情况下,快速排序比合并排序快的原因不是因为减少了开销,而是因为快速排序访问数据的方式,与标准合并排序相比,它对缓存更友好。
清晰的

@Clearer quicksort不是合并排序...您在14年12月21日回应的声明严格来说是关于合并排序及其是否稳定的。快速排序和更快的排序与您的评论或我的回复完全无关。对我来说讨论结束...一遍又一遍。
吉姆·巴尔特

3

答案会稍微倾向于quicksort,而不是DualPivotQuickSort为原始值带来的更改。它在JAVA 7中用于对java.util.Arrays进行排序

It is proved that for the Dual-Pivot Quicksort the average number of
comparisons is 2*n*ln(n), the average number of swaps is 0.8*n*ln(n),
whereas classical Quicksort algorithm has 2*n*ln(n) and 1*n*ln(n)
respectively. Full mathematical proof see in attached proof.txt
and proof_add.txt files. Theoretical results are also confirmed
by experimental counting of the operations.

-你可以在这里找到JAVA7 implmentation http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/util/Arrays.java

在DualPivotQuickSort进一步真棒阅读- http://permalink.gmane.org/gmane.comp.java.openjdk.core-libs.devel/2628


3

在归并排序中,通用算法为:

  1. 排序左子数组
  2. 排序正确的子数组
  3. 合并2个排序的子数组

在顶层,合并2个排序的子数组涉及处理N个元素。

在此之下一级,步骤3的每次迭代都涉及处理N / 2个元素,但是您必须重复此过程两次。因此,您仍在处理2 * N / 2 == N个元素。

在此之下一级,您正在合并4 * N / 4 == N个元素,依此类推。递归堆栈中的每个深度都涉及在该深度的所有调用中合并相同数量的元素。

请考虑使用快速排序算法:

  1. 选择一个枢轴点
  2. 将枢轴点放置在数组中的正确位置,所有较小的元素在左侧,较大的元素在右侧
  3. 排序左子数组
  4. 排序右子数组

在顶层,您正在处理大小为N的数组。然后选择一个枢轴点,将其放置在正确的位置,然后可以在算法的其余部分中完全忽略它。

在此之下一级,您正在处理两个子数组,它们的总大小为N-1(即减去先前的枢轴点)。您为每个子阵列选择一个枢轴点,最多可以有2个附加枢轴点。

由于与上述相同的原因,因此在这之下一级,您正在处理4个组合大小为N-3的子阵列。

然后是N-7 ...然后是N-15 ...然后是N-32 ...

递归堆栈的深度大致保持不变(logN)。使用merge-sort,您将始终在递归堆栈的每个级别上处理N个元素的合并。但是,通过快速排序,您要处理的元素数量会随着堆栈的减少而减少。例如,如果您查看递归堆栈中间的深度,则要处理的元素数为N-2 ^((logN)/ 2))== N-sqrt(N)。

免责声明:在归并排序时,由于每次将数组分为2个完全相等的块,因此递归深度为logN。在快速排序中,由于枢轴点不太可能恰好位于数组的中间,因此递归堆栈的深度可能比logN稍大。我还没有做数学运算来了解这个因素和上述因素实际上在算法的复杂性中起多大作用。


枢轴不是下一个级别的一部分,这也不是为什么QS表现更好的原因。请参阅其他答案以获得更多见解。
吉姆·巴尔特

@JimBalter您指的是“其他答案”?最高答案只是说QS“只需要很少的额外空间,并具有良好的缓存局部性”,但是没有说明为什么这样做,也没有提供任何引用。第二个答案只是说合并排序更适合较大的数据集
RvPr

您正在移动目标,从为什么QS表现更好到解释有关其运作方式的基本事实。回答其他问题的方法就是:stackoverflow.com/questions/9444714 / ... ...希望对您来说足够了;我不会再回应了。
吉姆·巴尔特19/06/14

3

与“合并排序”不同,“快速排序”不使用辅助空间。合并排序使用辅助空间O(n)。但是合并排序的最坏情况下的时间复杂度为O(nlogn),而快速排序的最坏情况下的复杂度为O(n ^ 2),这在数组已被排序时会发生。


不,当对数组进行排序时,QuickSort的最坏情况不会发生,除非您将第一个或最后一个项目用作数据透视表,但是没有人这样做。
吉姆·巴尔特

2

Quicksort具有更好的平均案例复杂性,但是在某些应用程序中,这是错误的选择。Quicksort容易受到拒绝服务攻击。如果攻击者可以选择要排序的输入,则他可以轻松构建一个集合,该集合的最坏情况下的时间复杂度为o(n ^ 2)。

Mergesort的平均大小写复杂度和最坏情况的复杂度是相同的,因此不会遇到相同的问题。归并排序的这一特性也使其成为实时系统的绝佳选择-正是因为没有病理情况导致它的运行速度大大降低。

由于这些原因,我比Mergesort更喜欢Mergesort。


2
Quicksort如何具有更好的平均案例复杂性?它们都是O(nlgn)。我认为,攻击者不会向任何排序算法提供输入信息……但是为了不让默默承担安全性,让我们假设他可以。尽管n ^ 2的运行时间比nlgn差,但不足以使Web服务器基于一次攻击而崩溃。实际上,DOS参数几乎为空,因为任何Web服务器都容易受到DDOS攻击,并且攻击者更有可能使用主机的分布式网络,所有TCP SYN都将泛洪。
CaTalyst.X 2013年

“ Quicksort具有更好的平均案例复杂性” –不,不是。
吉姆·巴尔特

2

很难说.MergeSort最差的是n(log2n)-n + 1,如果n等于2 ^ k(我已经证明了这一点),这是准确的。对于任何n,它都在(n lg n-n + 1)和(n lg n + n + O(lg n))。但是对于quickSort,最好是nlog2n(n等于2 ^ k)。如果将Mergesort除以quickSort,则当n为无穷大时等于1。好像MergeSort的最坏情况比QuickSort的最好情况要好,为什么我们要使用quicksort?但是请记住,MergeSort没有到位,它需要2n的memeroy空间。而且MergeSort还需要做很多数组拷贝,这总而言之,MergeSort确实比快速排序更吸引人,但实际上您需要考虑内存空间,数组复制的成本,合并要比快速排序慢。实验中,随机类为我提供了1000000个Java数字,mergesort用了2610毫秒,quicksort用了1370毫秒。


2

快速排序是最坏的情况O(n ^ 2),但是,平均情况始终一致执行合并排序。每种算法都是O(nlogn),但是您需要记住,在谈论Big O时,我们忽略了较低的复杂性因素。当涉及到恒定因素时,快速排序相对于合并排序具有重大改进。

合并排序还需要O(2n)内存,而快速排序可以就地完成(仅需要O(n))。这是快速排序通常优于合并排序的另一个原因。

额外信息:

快速排序的最坏情况发生在枢轴选择不当时。考虑以下示例:

[5,4,3,2,1]

如果将数据透视表选为组中的最小或最大数,则快速排序将以O(n ^ 2)进行。选择列表中最大或最小25%的元素的概率为0.5。这给算法带来了0.5个很好的机会。如果我们采用典型的枢轴选择算法(例如选择随机元素),则每次枢轴选择都有0.5个机会选择一个好的枢轴。对于较大的集合,始终选择差的轴点的概率为0.5 * n。基于此概率,对于平均(和典型)情况,快速排序是有效的。


O(2n)== O(n)。正确的说法是Mergesort需要O(n)个额外的内存(更具体地说,它需要n / 2个辅助内存)。对于链接列表,情况并非如此。
吉姆·巴尔特

@JimBalter主席先生,您是否愿意与我们分享有关他们的业绩的精彩而有价值的想法,作为对问题的答案?提前致谢。
snr

2

这是一个很老的问题,但是由于我最近都处理过这两个问题,所以这里是我的2c:

合并排序平均需要约N个log N比较。对于已经(几乎)排序的已排序数组,它降至1/2 N log N,因为在合并时,我们(几乎)总是选择1/2 N次“左侧”部分,然后仅复制右侧1/2 N个元素。另外,我可以推测,已经排序的输入使处理器的分支预测变量发光,但几乎可以正确猜测几乎所有分支,从而防止了管道停顿。

快速排序平均需要〜1.38 N log N个比较。从比较方面来说,它并不能从已经排序的数组中获得很大的好处(但是,在交换方面,甚至在CPU内部的分支预测方面,它都可以这样做)。

我在相当现代的处理器上的基准测试显示以下内容:

如果比较函数是回调函数(例如qsort()libc实现中的回调函数),则对随机输入而言,快速排序的速度比合并排序要慢15%,对于64位整数的已排序数组,快速排序要慢30%。

另一方面,如果比较不是回调,则我的经验是,快速排序比合并排序要好25%。

但是,如果您的(大)数组具有很少的唯一值,则无论如何合并合并排序都会开始超过快速排序。

因此,也许最重要的是:如果比较昂贵(例如,回调函数,比较字符串,比较结构的许多部分,大多数情况下达到“ if”的三分之一),那么您可能会变得更好与合并排序。对于更简单的任务,快速排序将更快。

话虽如此,所有上述说法都是正确的:-Quicksort可以是N ^ 2,但是Sedgewick声称,良好的随机化实现比雷击N ^ 2的计算机更有可能被闪电击中,-Mergesort需要额外的空间


如果比较便宜,即使排序输入,qsort也会击败mergesort吗?
Eonil

2

当我尝试两种排序算法时,通过计算递归调用的数量,与sortsort相比,quicksort始终具有较少的递归调用。这是因为quicksort有枢轴,并且枢轴不包含在下一个递归调用中。这样一来,快速排序可以比合并排序更快地到达递归基础案例。


枢轴与QS为何递归调用较少的原因无关...这是因为QS递归的一半是尾递归,可以将其消除。
吉姆·巴尔特

2

这是访谈中常见的问题,尽管合并排序在最坏的情况下表现更好,但快速排序被认为比合并排序更好,尤其是对于大量输入。由于某些原因,哪种快速排序更好:

1-辅助空间:快速排序是一种就地排序算法。就地排序意味着无需额外的存储空间即可执行排序。另一方面,合并排序需要一个临时数组来合并排序后的数组,因此它不是就地。

2-最坏的情况:O(n^2)使用随机快速排序可以避免快速排序的最坏情况。选择正确的枢轴可以很容易地避免这种情况。通过选择正确的枢轴元素来获得平均情况下的行为,使其即兴发挥性能并变得像合并排序一样高效。

3-引用位置: Quicksort特别具有良好的缓存位置,这使其在许多情况下(例如在虚拟内存环境中)比合并排序要快。

4-尾递归: QuickSort是尾递归,而合并排序则不是。尾递归函数是其中递归调用是该函数最后执行的函数。尾部递归函数比非尾部递归函数更好,因为可以通过编译器优化尾部递归。


1

虽然它们都在同一复杂度类中,但这并不意味着它们都具有相同的运行时。Quicksort通常比mergesort快,这是因为编写紧凑的实现更容易,而且它执行的操作可以更快。这是因为人们通常使用Quicksort而不是mergesort来更快。

然而!我个人经常会使用mergesort或quicksort变体,当quicksort表现不佳时,它会降级为mergesort。记得。Quicksort 平均仅为O(n log n)。最坏的情况是O(n ^ 2)!合并排序始终为O(n log n)。如果必须具有实时性能或响应能力,并且您的输入数据可能来自恶意源,则不应使用简单的快速排序。


1

在所有条件都相同的情况下,我希望大多数人使用最方便的方式,这往往是qsort(3)。除了快速排序以外,在数组上还非常快,就像mergesort是列表的常见选择一样。

我想知道的是为什么为什么很少见到基数或存储桶排序。它们是O(n),至少在链表上,并且所需要的只是某种将密钥转换为序数的方法。(字符串和浮点数很好用。)

我在想原因与计算机科学的教学有关。我什至不得不向我的算法分析讲师证明,确实有可能比O(n log(n))更快地排序。(他证明了你无法比较比O(n log(n))排序,这是对的。)

在其他新闻中,浮点数可以按整数排序,但是之后必须将负数转过来。

编辑:实际上,这是对floats-as-integers进行排序的一种更恶性的方法:http : //www.stereopsis.com/radix.html。请注意,无论您实际使用哪种排序算法,都可以使用位翻转技巧。


1
我看过我的基数排序。但是它很难使用,因为如果分析正确,它的运行时就不会是O(n),因为它所依赖的不仅仅是输入元素的数量。通常,很难对基数做出需要对输入进行有效排序的强预测。
Konrad Rudolph

O(n),其中n是输入大小,即包括元素的大小。确实可以实现它,所以必须填充很多零,但是使用比较差的实现是没有意义的。(也就是说,实施可能很难,嗯)
Anders Eurenius

请注意,如果您使用的是GNU libc,qsort则是一种合并排序。
杰森·奥伦多夫

嗯,确切地说,这是一种合并排序,除非无法分配必要的临时内存。cvs.savannah.gnu.org/viewvc/libc/stdlib/…–
Jason

1

快速与合并排序的少量补充。

也可以取决于排序项目的种类。如果访问项目,交换和比较不是简单的操作,例如比较平面内存中的整数,则合并排序可能是首选算法。

例如,我们使用远程服务器上的网络协议对项目进行排序。

此外,在“链接列表”之类的自定义容器中,快速排序也无济于事。
1.合并链表上的排序,不需要额外的内存。2.快速访问元素不是连续的(在内存中)


0

快速排序是一种就地排序算法,因此它更适合于数组。另一方面,合并排序需要O(N)的额外存储,并且更适合链接列表。

与数组不同,在喜欢的列表中,我们可以在O(1)空间和O(1)时间的中间插入项目,因此可以在没有任何额外空间的情况下实现合并排序中的合并操作。但是,为数组分配和取消分配额外的空间会对合并排序的运行时间产生不利影响。合并排序还有利于链接列表,因为顺序访问数据无需太多随机内存访问。

另一方面,快速排序需要大量的随机内存访问,使用数组,我们可以直接访问内存,而无需按照链表的要求进行任何遍历。当用于数组时,快速排序也具有很好的引用位置,因为数组连续存储在内存中。

即使两种排序算法的平均复杂度为O(NlogN),通常用于普通任务的人员都使用数组进行存储,因此,快速排序应成为首选算法。

编辑:我刚刚发现合并排序最坏/最佳/平均情况总是nlogn,但是快速排序可以从n2(元素已经排序时的最坏情况)到nlogn(当数据透视始终将数组分为两部分时的平均/最好情况)一半)。


0

同时考虑时间和空间的复杂性。对于合并排序:时间复杂度:O(nlogn),空间复杂度:O(nlogn)

快速排序:时间复杂度:O(n ^ 2),空间复杂度:O(n)

现在,他们俩都各自赢得一场胜利。但是,使用随机数据透视表,您几乎总是可以将快速排序的时间复杂度降低为O(nlogn)。

因此,在许多应用程序中,快速排序是首选,而不是合并排序。


-1

在c / c ++领域中,当不使用stl容器时,我倾向于使用quicksort,因为它是运行时内置的,而mergesort不是。

因此,我认为,在许多情况下,这只是阻力最小的途径。

此外,对于整个数据集不适合工作集的情况,使用快速排序可以提高性能。


3
实际上,如果您正在谈论的是qsort()库函数,则可能会或可能不会将其实现为quicksort。
Thomas Padron-McCarthy

3
康拉德,对此感到遗憾,但是您在哪里可以找到保证?我在ISO C标准或C ++标准中找不到它。
Thomas Padron-McCarthy

2
GNU libc qsort是一种合并排序,除非元素的数量确实是巨大的或无法分配临时内存。cvs.savannah.gnu.org/viewvc/libc/stdlib/…–
Jason

-3

原因之一是更具哲学性。Quicksort是自上而下的理念。有n个要排序的元素,就有n个!可能性。通过m和nm的两个分区互斥,可能性的数量下降了几个数量级。米!*(nm)!比n小几倍!单独。想象5!vs 3!* 2 !. 5!比2个分区2和3的可能性多10倍。并推断为100万阶乘与900K!* 100K!与之相对,因此不必担心在范围或分区内建立任何顺序,而只需在更广泛的分区内建立顺序,并减少分区内的可能性。如果分区本身不是互斥的,则在该范围内较早建立的任何顺序将在以后受到干扰。

诸如合并排序或堆排序之类的任何自下而上的方法都类似于工人或雇员的方法,在这种方法中,人们开始在微观层面进行比较。但是,一旦稍后在它们之间找到一个元素,就必然会丢失该顺序。这些方法非常稳定且非常可预测,但是需要做一些额外的工作。

快速排序就像管理方法,其中一开始不关心任何订单,而只是满足一个广泛的标准而无需考虑订单。然后将分区缩小,直到获得排序集。Quicksort的真正挑战在于,当您对排序元素一无所知时,在黑暗中找到一个分区或标准。这就是为什么我们需要花费一些精力来查找中值或以随机或任意“管理”方法选择1的原因。要找到理想的中位数可能需要花费大量精力,并再次导致愚蠢的自下而上方法。因此,Quicksort说,只是随机选择一个枢轴,希望它会出现在中间某处,或者做一些工作以找到3,5的中位数,或者找到更多的值以找到更好的中位数,但不打算做到完美&不要 不要在最初订购时浪费任何时间。如果您很幸运,或者当您没有中位数而只是抓住机会时有时降级到n ^ 2的话,这似乎很好。任何方式的数据都是随机的。对。因此,我更同意快速排序的自上而下的逻辑方法,事实证明,与之相比,任何细致而彻底的稳定自下而上的方法,像之前节省的枢轴选择和比较这样的机会似乎可以更好地工作。合并排序。但 较早保存的比较似乎比任何细致而彻底的稳定的自下而上的方法(如合并排序)效果更好。但 较早保存的比较似乎比任何细致而彻底的稳定的自下而上的方法(如合并排序)效果更好。但


quicksort受益于枢轴选择的随机性。随机枢轴自然会趋向于50:50的划分,并且不太可能始终如一地走向极端。nlogn的恒定因子相当低,直到平均分配为60-40甚至达到70-30。
冬瓜

这是完全废话。使用quicksort是因为其性能,而不是“哲学”……而有关“秩序必然会丢失”的主张完全是错误的。
吉姆·巴尔特
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.