Quicksort vs heapsort
快速sorting和堆sorting都可以进行sorting。 哪个更好? 什么是优先的应用和案例?
http://www.cs.auckland.ac.nz/~jmor159/PLDS210/qsort3.html有一些分析。;
另外,从维基百科:
quicksort最直接的竞争对手是heapsort。 heapsort通常比quicksort慢一些,但是最坏情况下的运行时间总是Θ(nlogn)。 Quicksort速度通常更快,但除了introsort变种,在检测到不好的情况时切换到heapsort时,仍然存在性能最差的机会。 如果事先知道heapsort是必要的,直接使用它将比等待introsort切换到它更快。
heapsort是O(N log N)保证,比Quicksort中的最坏情况好得多。 Heapsort不需要更多的内存来存放另一个数组,以按Mergesort所需要的顺序排列数据。 那么为什么商业应用程序坚持使用Quicksort? 什么QuickSort有什么特别优于其他实现?
我已经testing了algorithm,我已经看到,Quicksort确实有一些特别的东西。 它运行速度快,比堆和合并algorithm快得多。
Quicksort的秘密是:它几乎不做不必要的元素交换。 交换是耗时的。
使用Heapsort,即使您的所有数据已经sorting,您也要交换100%的元素来排列数组。
使用Mergesort,情况更糟。 即使已经预定了数据,你仍然要写另一个数组中100%的元素,并将其写回到原始数组中。
使用Quicksort,您不必交换已经订购的产品。 如果你的数据是完全有序的,你几乎没有交换! 尽pipe最糟糕的情况有很多值得关注的地方,但是对于数据透视的select,除了得到数组的第一个或最后一个元素之外,还有一点改进可以避免。 如果从第一个元素,最后一个元素和中间元素之间的中间元素获得一个枢轴,则避免最坏的情况是不够的。
Quicksort的优越性不是最坏的情况,而是最好的情况! 在最好的情况下,你做相同数量的比较,好的,但你几乎没有交换。 在平均情况下,您可以交换部分元素,但不是所有元素,例如Heapsort和Mergesort。 这就是Quicksort最好的时机。 交换更less,速度更快。
在我的计算机上,在C#中,在发布模式下运行的实现比ArraySort在中间轴上的时间增加了3秒,而在改进后的轴上的时间增加了2秒(是的,有一个很好的调整的开销)。
static void Main(string[] args) { int[] arrToSort = new int[100000000]; var r = new Random(); for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length); Console.WriteLine("Press q to quick sort, s to Array.Sort"); while (true) { var k = Console.ReadKey(true); if (k.KeyChar == 'q') { // quick sort Console.WriteLine("Beg quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff")); QuickSort(arrToSort, 0, arrToSort.Length - 1); Console.WriteLine("End quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff")); for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length); } else if (k.KeyChar == 's') { Console.WriteLine("Beg Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff")); Array.Sort(arrToSort); Console.WriteLine("End Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff")); for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length); } } } static public void QuickSort(int[] arr, int left, int right) { int begin = left , end = right , pivot // get middle element pivot //= arr[(left + right) / 2] ; //improved pivot int middle = (left + right) / 2; int LM = arr[left].CompareTo(arr[middle]) , MR = arr[middle].CompareTo(arr[right]) , LR = arr[left].CompareTo(arr[right]) ; if (-1 * LM == LR) pivot = arr[left]; else if (MR == -1 * LR) pivot = arr[right]; else pivot = arr[middle]; do { while (arr[left] < pivot) left++; while (arr[right] > pivot) right--; if(left <= right) { int temp = arr[right]; arr[right] = arr[left]; arr[left] = temp; left++; right--; } } while (left <= right); if (left < end) QuickSort(arr, left, end); if (begin < right) QuickSort(arr, begin, right); }
对于大多数情况来说,快速对比快一点是无关紧要的……你根本就不想让它偶尔慢一点。 虽然您可以调整QuickSort以避免缓慢的情况,但您将失去基本QuickSort的优雅。 所以,对于大多数情况,我更喜欢HeapSort …你可以用它简单的优雅来实现它,而且从来没有慢过。
对于大多数情况下你想要最大速度的情况,QuickSort可能比HeapSort更受欢迎,但都不是正确的答案。 对于速度危急的情况,值得仔细检查情况的细节。 例如,在一些速度至关重要的代码中,数据已经被sorting或接近sorting是非常常见的(它将多个相关字段编入索引,这些字段往往会一起上下移动,或者上下移动,所以一旦你sorting一,其他人要么sorting或反向sorting或closures…其中任何一个可以杀死快速sorting)。 在这种情况下,我没有实现…而是实现了Dijkstra的SmoothSort …一个HeapSort变体,当它已经sorting或接近sorting时,它是O(N)…它不那么优雅,不太容易理解,但速度快…阅读http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF如果你想要一些更具挑战性的代码。;
Quicksort-Heapsort就地混合器也很有趣,因为它们中的大多数在最坏的情况下只需要n * log n比较(它们对于渐近线的第一项是最优的,所以它们避免了最坏情况),O(log n)额外空间,并且它们相对于已经有序的数据集至less保留了Quicksort的良好行为的“一半”。 Dikert和Weiss在http://arxiv.org/pdf/1209.4214v1.pdf中介绍了一个非常有趣的algorithm:;
- select一个枢轴p作为sqrt(n)元素的随机样本的中值(这可以通过Tarjan&co的algorithm进行至多24 sqrt(n)的比较,或者通过更复杂的蜘蛛进行5 sqrt(n)的比较Schonhage的工厂algorithm);
- 像在Quicksort的第一步一样,将数组分成两部分。
- 对最小的部分进行堆积,并使用O(log n)额外的位来编码一个堆,其中每个左边的孩子的值都大于其兄弟;
- recursion地提取堆的根,筛选出根的左边的lacune,直到它到达堆的叶,然后用从数组的另一部分取出的适当的元素填充lacune;
- 重新计算数组中剩余的非有序部分(如果selectp作为确切的中位数,则根本没有recursion)。
比较。 在quick sort
和merge sort
之间,因为两者都是就地sorting的types,所以在quick sort
情况下,运行时间的复杂度是O(n^2)
,对于堆sorting,它仍然是O(n*log(n))
和平均数量的数据快速sorting将会更有用。 由于它是随机algorithm,所以得到正确答案的概率。 在较短的时间内将取决于你select的枢轴元素的位置。
所以a
良好的呼叫: L和G的大小都小于3s / 4
坏呼叫: L和G之一的大小大于3s / 4
less量的我们可以去插入sorting和非常大量的数据去堆sorting。
Heapsort构build一个堆,然后重复提取最大项目。 最坏的情况是O(n log n)。
但是,如果你看到最快的sorting ,即O(n2),你会意识到快速sorting对大数据来说不是一个好的select。
所以这使得sorting是一件有趣的事情; 我相信今天分类algorithm如此之多的原因是因为它们都是最好的地方。 例如,如果对数据进行sorting,则冒泡sorting可以执行快速sorting。 或者,如果我们知道一些关于要sorting的项目,那么可能我们可以做得更好。
这可能不会直接回答你的问题,以为我会加两分钱。
Heapsort具有O(n * log(n))的最差运行情况的好处,所以在快速sorting可能performance不佳的情况下(通常大多数sorting的数据集)heapsort是非常优选的。
在处理非常大的input时,堆sorting是安全的。 渐近分析揭示了Heapsort在最坏情况下的增长顺序是Big-O(n logn)
,这比Quicksort的Big-O(n^2)
要好。 然而,在大多数机器上, Heapsort在实践中稍慢于实施良好的快速sorting。 Heapsort也不是一个稳定的sortingalgorithm。
在实践中heapsort的原因比quicksort慢,这是由于quicksort中的数据元素位于相对靠近的存储位置内的更好的引用位置(“ https://en.wikipedia.org/wiki/Locality_of_reference ”)。 显示强参考位置的系统是性能优化的绝佳select。 堆sorting,但是,处理更大的飞跃。 这使得quicksort更适合于较小的投入。
那么,如果你去了架构层面…我们在caching内存中使用队列数据结构。所以在队列中可用的将被sorting。在快速sorting中,我们没有问题将数组分成任何长度…但在堆sorting(通过使用数组)可能会发生这样的情况:父类可能不存在于caching中可用的子数组中,然后它必须将其放入caching内存,这非常耗时。 这是quicksort是最好的!!😀
对我来说,heapsort和quicksort之间有一个非常根本的区别:后者使用recursion。 在recursionalgorithm中,堆随着recursion的数量而增长。 这个并不重要,如果n很小,但现在我正在sorting两个matrixn = 10 ^ 9 !! 该程序需要几乎10 GB的RAM和任何额外的内存将使我的电脑开始交换到虚拟磁盘内存。 我的磁盘是一个RAM磁盘,但仍然交换它在速度上有很大的不同 。 所以在一个用C ++编写的包含可调维matrix的statpack中,程序员事先未知的大小,以及非参数统计types的sorting,我更喜欢使用heapsort来避免延迟使用非常大的数据matrix。
回答原来的问题,并在这里解决一些其他的意见:
我只是比较了select,快速,合并和堆sorting的实现,看看它们如何相互叠加。 答案是他们都有缺点。
TL; DR:Quick是最好的通用sorting(合理快速,稳定,大部分就地)。我个人更喜欢堆sorting,除非我需要一个稳定的sorting。
select – N ^ 2 – 只有不到20个元素才有用,那么它的performance就会跑赢大盘。 除非你的数据已经被sorting,或者非常非常接近。 N ^ 2真的很慢。
根据我的经验,很快就不是那么快。 使用快速sorting作为一般sorting的奖金是,它是相当快,它是稳定的。 这也是一个就地algorithm,但是因为它通常是recursion实现的,所以它将占用额外的堆栈空间。 它也落在O(n log n)和O(n ^ 2)之间。 时机似乎证实了这一点,特别是当价值在一个狭窄的范围内。 这比在10,000,000个项目上selectsorting要快,但是比合并或堆缓慢。
由于sorting不依赖于数据,所以合并sorting保证为O(n log n)。 它只是做它的事情,不pipe你给了什么价值。 它也是稳定的,但是如果你不小心执行的话,很多种类的东西都会把你的堆栈炸掉。 有一些复杂的就地合并sorting实现,但通常你需要在每个级别的另一个数组合并你的值。 如果这些arrays存在于堆栈中,则可能会遇到问题。
堆sorting最大为O(n日志n),但在许多情况下更快,这取决于您需要将日志深度堆中的值移动多远。 堆可以很容易地在原始数组中就地实现,所以它不需要额外的内存,而且是迭代的,所以在recursion时不用担心堆栈溢出。 堆sorting的巨大缺点是它不是一个稳定的sorting,这意味着如果你需要的话,它是正确的。