面试时有人问了我这个问题。它们都是O(nlogn)的,但大多数人使用快速排序而不是归并排序。为什么呢?
面试时有人问了我这个问题。它们都是O(nlogn)的,但大多数人使用快速排序而不是归并排序。为什么呢?
快速排序的平均时间复杂度较好,但在某些应用中选择它是错误的选择。快速排序容易受到拒绝服务攻击。如果攻击者可以选择要排序的输入,他可以轻易地构造一个需要o(n^2)最坏情况时间复杂度的集合。
归并排序的平均时间复杂度和最坏时间复杂度相同,因此不会出现同样的问题。归并排序的这一特性也使其成为实时系统的优选 - 恰恰因为没有导致它运行缓慢的病态情况。
出于这些原因,我更喜欢归并排序而不是快速排序。
快速排序和归并排序的小补充。
另外,这也取决于排序项的种类。如果访问项、交换和比较不是简单的操作,比如在平面内存中比较整数,那么归并排序可能是更好的算法。
例如,我们使用远程服务器上的网络协议对项目进行排序。
此外,在自定义容器(如“链表”)中,快速排序没有任何好处。
1. 在链表上进行归并排序时,不需要额外的内存。
2. 快速排序中的元素访问不是连续的(在内存中)。
一切条件相等的情况下,我预期大多数人会使用最方便的方法,而qsort(3)往往是这样的选择。除此之外,快速排序在数组上非常快,就像合并排序对于列表来说是常见的选择一样。
我想知道的是为什么 基数排序 或桶排序如此罕见。它们都是O(n),至少对于链表而言,只需要一些将键转换为序数的方法即可。(字符串和浮点数也可以正常工作)
我认为原因与计算机科学教育有关。我甚至不得不向我的算法分析讲师证明比O(n log(n))更快的排序确实是存在的。(他有一个证明,你不能比较排序快于O(n log(n)),这是正确的。)
另外,浮点数可以作为整数进行排序,但排序后必须将负数转回。
编辑: 实际上,这是一种更加恶劣的将浮点数作为整数排序的方法:http://www.stereopsis.com/radix.html。请注意,无论您实际使用什么排序算法,都可以使用位翻转技巧...
qsort
是一种归并排序。 - Jason Orendorff虽然它们都属于同一复杂度类,但这并不意味着它们的运行时间相同。快速排序通常比归并排序更快,因为编写紧凑实现并且执行的操作可以更快。正是因为快速排序通常更快,人们才使用它而不是归并排序。
然而!我个人经常会使用归并排序或者快速排序变体,当快速排序表现不佳时会退化到归并排序。记住,快速排序只有在平均情况下才是O(n log n)。最坏情况下是O(n^2)! 归并排序始终是O(n log n)。在实时性能或响应性是必须的情况下,如果您的输入数据可能来自恶意来源,则不应使用普通快速排序。
快速排序是一种原地排序算法,因此更适合于数组。另一方面,归并排序需要额外的O(N)存储空间,更适合于链表。
与数组不同,在链表中我们可以在中间插入项,其空间和时间复杂度均为O(1),因此归并排序中的合并操作可以在没有任何额外空间的情况下实现。然而,为数组分配和释放额外空间对归并排序的运行时间有不利影响。归并排序也更喜欢链表,因为数据是按顺序访问的,没有太多随机内存访问。
另一方面,快速排序需要大量的随机内存访问,使用数组时我们可以直接访问内存,而无需像链表那样进行遍历。此外,当用于数组时,快速排序具有良好的引用局部性,因为数组在内存中是连续存储的。
尽管这两种排序算法的平均复杂度都是O(NlogN),但通常人们在普通任务中使用数组进行存储,因此快速排序应该是首选算法。
编辑:我刚刚发现归并排序的最坏/最佳/平均情况始终为nlogn,但快速排序的复杂度可以从n2(当元素已经排序时的最坏情况)到nlogn(当枢轴始终将数组分成两半的平均/最佳情况)。
考虑时间和空间复杂度。 对于归并排序: 时间复杂度:O(nlogn), 空间复杂度:O(nlogn)
对于快速排序: 时间复杂度:O(n^2), 空间复杂度:O(n)
现在,它们各自在一个场景中胜出。 但是,使用随机枢轴,您几乎总能将快速排序的时间复杂度降至O(nlogn)。
因此,在许多应用程序中,快速排序比归并排序更受青睐。
qsort
是一种归并排序,除非元素数量真正巨大或无法分配临时内存。http://cvs.savannah.gnu.org/viewvc/libc/stdlib/msort.c?revision=1.21.2.2&root=libc&view=markup - Jason Orendorff
qsort
、Python的list.sort
以及Firefox JavaScript中的Array.prototype.sort
都是强化版的归并排序。(GNU STL的sort
使用Introsort,但这可能是因为在C++中,交换操作可能比复制操作更加高效。) - Jason Orendorff