我在一次面试中被问到这个问题。它们都是O(nlogn),但大多数人使用快速排序而不是归并排序。为什么呢?


当前回答

亩! 快速排序并不比归并排序更好,它非常适合于不同类型的应用。

归并排序是值得考虑的,如果速度是本质,糟糕的最差情况性能不能容忍,并且有额外的空间可用

你说他们«他们都是O(nlogn)[…]»。这是错误的。«快速排序使用大约n^2/2比较在最坏的情况下。

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

1 Sedgewick,算法

其他回答

正如许多人所注意到的,快速排序的平均情况性能要比归并排序快。但这只适用于假设按需访问任何内存段的时间为常数的情况。

在RAM中,这种假设通常不太坏(由于缓存的存在,这种假设并不总是正确的,但也不太坏)。然而,如果你的数据结构足够大,可以存储在磁盘上,那么快速排序就会因为磁盘平均每秒进行200次随机查找而被扼杀。但是,同样的磁盘在按顺序每秒读取或写入兆字节的数据方面没有任何问题。这正是归并排序所做的。

因此,如果数据必须在磁盘上排序,你真的,真的想使用归并排序的一些变体。(通常你快速排序子列表,然后开始将它们合并到某个大小阈值以上。)

Furthermore if you have to do anything with datasets of that size, think hard about how to avoid seeks to disk. For instance this is why it is standard advice that you drop indexes before doing large data loads in databases, and then rebuild the index later. Maintaining the index during the load means constantly seeking to disk. By contrast if you drop the indexes, then the database can rebuild the index by first sorting the information to be dealt with (using a mergesort of course!) and then loading it into a BTREE datastructure for the index. (BTREEs are naturally kept in order, so you can load one from a sorted dataset with few seeks to disk.)

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

我想补充的是,到目前为止提到的三种算法(归并排序,快速排序和堆排序)只有归并排序是稳定的。也就是说,对于那些具有相同键的值,顺序不会改变。在某些情况下,这是可取的。

但是,说实话,在实际情况下,大多数人只需要良好的平均性能和快速排序…快速=)

所有排序算法都有其起伏。有关排序算法的概述,请参阅维基百科的文章。

这是一个相当老的问题,但因为我最近处理了这两个问题,所以这里是我的2c:

归并排序平均需要~ N log N次比较。对于已经(几乎)排序过的排序数组,这可以达到1/ 2nlog N,因为在归并时,我们(几乎)总是选择“左边”的1/ 2n次,然后只复制右边1/ 2n个元素。此外,我可以推测,已经排序的输入使处理器的分支预测器发光,但猜测几乎所有的分支都正确,从而防止管道停顿。

快速排序平均需要~ 1.38 nlog N个比较。在比较方面,它不会从已经排序的数组中获得很大的好处(但是在交换方面,可能在CPU内部的分支预测方面,它会获得很大的好处)。

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

当比较函数是回调函数时(如qsort() libc实现),对于随机输入,快速排序比归并排序慢15%,对于已经排序的64位整数,快排序比归并排序慢30%。

另一方面,如果比较不是回调,我的经验是快速排序优于归并排序高达25%。

然而,如果你的(大)数组只有很少的唯一值,归并排序在任何情况下都开始超过快速排序。

因此,底线可能是:如果比较是昂贵的(例如,回调函数,比较字符串,比较结构的许多部分,主要是得到第二个,第三个,第四个“if”来产生差异)-很可能你会更好地使用归并排序。对于简单的任务,快速排序会更快。

之前所说的都是真的: -快速排序可以是N^2,但Sedgewick声称,一个好的随机实现有更多的机会,计算机执行排序被闪电击中比N^2 —归并排序需要占用额外空间

在归并排序中,一般算法为:

对左子数组进行排序 对右子数组进行排序 合并两个已排序的子数组

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

再往下一层,第3步的每次迭代都涉及处理N/2个元素,但您必须重复此过程两次。所以你仍然在处理2 * N/2 == N个元素。

再往下一层,你要合并4 * N/4 == N个元素,以此类推。递归堆栈中的每个深度都涉及合并相同数量的元素,涉及对该深度的所有调用。

考虑一下快速排序算法:

选择一个枢轴点 将枢轴点放置在数组中的正确位置,所有较小的元素放在左边,较大的元素放在右边 对左子数组进行排序 对右子数组排序

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

再往下一层,您将处理2个子数组,它们的组合大小为N-1(即减去之前的枢轴点)。为每个子数组选择一个枢轴点,总共有2个额外的枢轴点。

再往下一层,您将处理4个子数组,它们的组合大小为N-3,原因与上面相同。

然后N-7…然后c15…然后N-32…

递归堆栈的深度保持大致相同(logN)。使用归并排序,你总是在递归堆栈的每一层处理n个元素的归并。但是使用快速排序,你要处理的元素数量会随着你在堆栈中向下移动而减少。例如,如果你在递归堆栈中查看深度,你正在处理的元素数量是N - 2^((logN)/2)) == N -根号(N)。

声明:对于归并排序,因为每次都将数组分割为两个完全相等的块,所以递归深度正好是logN。在快速排序时,由于枢轴点不太可能恰好位于数组的中间,因此递归堆栈的深度可能略大于logN。我还没有做过数学计算,看看这个因素和上面描述的因素在算法复杂性中究竟扮演了多大的角色。

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

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

现在,他们各自在一个场景中获胜。 但是,使用随机枢轴,您几乎总是可以将快速排序的时间复杂度降低到O(nlogn)。

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