Answers:
本文进行了一些分析。
另外,来自维基百科:
快速排序最直接的竞争对手是堆排序。堆排序通常比快速排序慢一些,但最坏的运行时间始终是Θ(nlogn)。Quicksort通常更快,但是除了introsort变体之外,还有可能出现最坏情况的性能,当introsort变体在检测到不良情况时切换到堆排序。如果事先知道有必要使用堆排序,那么直接使用它比等待内向排序切换到它要快。
Heapsort是O(N log N)保证的,这比Quicksort中最差的情况要好得多。Heapsort不需要更多的内存来放置另一个数组,就可以像Mergesort那样放置有序数据。那么,为什么商业应用程序坚持使用Quicksort?与其他实现相比,Quicksort有什么特别之处?
我自己测试了算法,并且发现Quicksort确实有一些特别之处。它运行速度快,比堆和合并算法快得多。
Quicksort的秘密是:它几乎不执行不必要的元素交换。交换非常耗时。
使用Heapsort,即使您已对所有数据进行了排序,您也将交换100%的元素来对数组进行排序。
使用Mergesort,情况甚至更糟。您将要在另一个数组中写入100%的元素,然后将其写回到原始数组中,即使已经订购了数据。
使用Quicksort,您无需交换已订购的产品。如果您的数据已完全订购,则几乎不交换任何信息!尽管对于最坏的情况有很多麻烦,但对支点的选择稍作改进,除获得数组的第一个或最后一个元素外,都可以避免。如果从中间元素在第一个,最后一个和中间元素之间获取轴心,则足以避免发生最坏情况。
Quicksort的优势不是最坏的情况,而是最好的情况!在最佳情况下,您可以进行相同数量的比较,好的,但是您几乎不交换任何内容。通常,您交换部分元素,但不是全部元素,如Heapsort和Mergesort。这就是给Quicksort最好的时间。更少的交换,更快的速度。
下面的C#在我的计算机上以释放模式运行的实现在运行中比Array.Sort在中间枢轴下要慢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更为可取,但都不是正确的答案。对于紧急情况,值得仔细检查情况的细节。例如,在我的一些对速度有严格要求的代码中,数据已经被排序或接近排序是很常见的(它为多个相关字段建立索引,这些字段通常一起上下移动或彼此上下移动,因此,一旦您按一个排序,其他的就会被排序,反向排序或接近……其中任何一个都可以杀死QuickSort)。在那种情况下,我都没有实现...而是实现了Dijkstra的SmoothSort ... HeapSort变体,在已经排序或接近排序时为O(N)...它不是那么优雅,不太容易理解,但是很快...阅读如果您想要编写更具挑战性的代码,请访问http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF。
Quicksort-Heapsort就地混合也非常有趣,因为它们大多数在最坏的情况下只需要n * log n比较(就渐近的第一项而言,它们是最佳的,因此它们避免了最坏的情况O(log n)的额外空间,它们相对于已排序的数据集至少保留了Quicksort良好行为的“一半”。Dikert和Weiss在http://arxiv.org/pdf/1209.4214v1.pdf中提出了一种非常有趣的算法:
比较 之间的quick sort
,merge sort
因为两者都是就地排序的类型,因此,对于快速排序而言,wrost案例运行时间与wrost案例运行时间之间是有区别的;O(n^2)
对于堆排序而言,它仍然是这样O(n*log(n))
;对于平均数量的数据而言,快速排序更为有用。由于它是随机算法,因此获得正确ans的可能性。更少的时间取决于您选择的枢轴元素的位置。
所以
好电话: L和G的大小均小于3s / 4
错误通话: L和G中的一个大小大于3s / 4
对于少量,我们可以进行插入排序,对于大量数据,可以进行堆排序。
堆排序的好处是运行情况最糟的情况是O(n * log(n)),因此,在快速排序执行效果可能很差的情况下(通常是大多数排序的数据集),堆排序是更可取的。
好吧,如果您进入架构级别...我们在高速缓存中使用队列数据结构,因此队列中可用的内容都将被排序。作为快速排序,我们将数组划分为任何长度都没有问题...但是在堆中排序(通过使用数组)可能会导致父级可能不存在于缓存中可用的子数组中,然后必须将其带入缓存中……这很费时间。这是最好的快速排序!!😀
堆排序构建一个堆,然后重复提取最大项。最坏的情况是O(n log n)。
但是,如果您看到快速排序的最坏情况是O(n2),您将意识到快速排序对于大数据而言不是一个很好的选择。
因此,这使排序是一件有趣的事情;我相信今天有这么多排序算法存在的原因是因为它们在最佳位置都是“最佳”的。例如,如果对数据进行排序,气泡排序可以执行快速排序。或者,如果我们对要排序的项目有所了解,那么我们可能会做得更好。
可能不会直接回答您的问题,以为我要加两分钱。
当处理非常大的输入时,堆排序是一个安全的选择。渐近分析显示,最坏情况下Heapsort的增长顺序为Big-O(n logn)
,比Big-O(n^2)
最坏情况下的Quicksort更好。但是,堆排序在大多数机器上在实践中都比快速实现的排序要慢一些。堆排序也不是稳定的排序算法。
实际上,堆排序比速排序更慢的原因是由于速排序中的引用元素(“ https://en.wikipedia.org/wiki/Locality_of_reference ”)的位置更好,数据元素位于相对较近的存储位置内。表现出强烈的参考性的系统是性能优化的理想选择。但是,堆排序具有更大的飞跃。这使得快速排序更适合较小的输入。
对我来说,堆排序和快速排序之间有一个非常根本的区别:快速排序使用递归。在递归算法中,堆随着递归次数的增长而增长。如果n小,这无关紧要,但是现在我正在对两个矩阵进行排序,其中n = 10 ^ 9 !!。该程序占用了将近10 GB的内存,任何多余的内存都将使我的计算机开始交换到虚拟磁盘内存。我的磁盘是RAM磁盘,但是仍然交换到它会大大改变速度。因此,在用C ++编码的statpack中,它包括可调整的维数矩阵,程序员事先不知道大小,以及非参数统计排序,我更喜欢使用heapsort以避免对非常大的数据矩阵使用的延迟。
要回答原始问题并在此处解决其他一些评论:
我只是比较了选择,快速,合并和堆排序的实现,以了解它们如何相互叠加。答案是它们都有缺点。
TL; DR:Quick是最好的通用排序(合理地快速,稳定并且大部分就位),但我个人更喜欢堆排序,除非需要稳定排序。
选择-N ^ 2-实际上只有少于20个左右的元素才有用,然后表现不佳。除非您的数据已经排序,或者非常非常接近。N ^ 2变慢,变快。
根据我的经验,速度并不是一直都那么快。使用快速排序作为常规排序的好处是它相当快且稳定。它也是一个就地算法,但是由于通常是递归实现的,因此会占用额外的堆栈空间。它也落在O(n log n)和O(n ^ 2)之间。某种程度上的计时似乎可以证实这一点,尤其是当值落在狭窄范围内时。它比对10,000,000个项目的选择排序更快,但比合并或堆慢。
合并排序是有保证的O(n log n),因为其排序与数据无关。不管您赋予它什么值,它都会做它所做的事情。它也很稳定,但是如果您对实现不小心的话,很大的种类会炸毁您的堆栈。有一些复杂的就地合并排序实现,但是通常您需要在每个级别中使用另一个数组将值合并到其中。如果这些数组存在于堆栈中,则可能会遇到问题。
堆排序最大为O(n log n),但在许多情况下,速度更快,这取决于将值沿log n深堆向上移动的距离。堆可以很容易地在原始数组中就地实现,因此它不需要额外的内存,而且是迭代的,因此不必担心递归时的堆栈溢出。堆排序的一个巨大缺点是它不是一个稳定的排序,这意味着如果需要它是正确的。