Quicksort vs heapsort

时间:2010-03-18 05:45:44

标签: algorithm sorting quicksort heapsort

quicksort和heapsort都进行就地排序。哪个更好?什么是首选的应用程序和案例?

12 个答案:

答案 0 :(得分:82)

Heapsort是O(N log N)保证,比Quicksort的最坏情况要好得多。根据Mergesort的需要,Heapsort不需要为另一个阵列提供更多内存来放置有序数据。那么为什么商业应用程序坚持使用Quicksort? Quicksort有什么特别优于其他实现?

我自己测试过这些算法,而且我已经看到Quicksort确实有一些特别之处。它运行速度快,比Heap和Merge算法快得多。

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);
}

答案 1 :(得分:46)

This paper有一些分析。

另外,来自维基百科:

  

最直接的竞争对手   quicksort是heapsort。 Heapsort是   通常有点慢   快速排序,但最坏的情况下运行   时间总是Θ(nlogn)。 Quicksort是   通常更快,但仍有   最坏情况表现的可能性   除了inososort变种,其中   当一个坏的情况下切换到heapsort   被检测到。如果事先知道的话   那个heapsort会是   必要的,直接使用它   比等待内向更快   切换到它。

答案 2 :(得分:14)

对于大多数情况来说,快速与快速相关是无关紧要的......你根本不想让它偶尔变得缓慢。虽然您可以调整QuickSort以避免缓慢的情况,但您会失去基本QuickSort的优雅。所以,对于大多数事情,我实际上更喜欢HeapSort ......你可以用它完全简单的优雅来实现它,而且永远不会慢慢排序。

对于大多数情况下你想要最大速度的情况,QuickSort可能比HeapSort更受欢迎,但两者都不是正确的答案。对于速度危急的情况,值得仔细研究情况的细节。例如,在我的一些速度关键代码中,数据已经排序或接近排序是很常见的(它正在索引多个相关字段,这些字段通常一起上下移动或上下移动,所以一旦你按一个排序,其他的排序或反向排序或关闭...其中任何一个都可以杀死QuickSort)。对于那种情况,我没有实现......相反,我实现了Dijkstra的SmoothSort ...一个HeapSort变体,当已经排序或接近排序时是O(N)......它不是那么优雅,不太容易理解,但是快...如果你想要一些更具挑战性的代码,请阅读http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF

答案 3 :(得分:5)

Quicksort-Heapsort就地混合动力车也非常有趣,因为在最坏的情况下它们大多只需要n * log n比较(它们在渐近线的第一项中是最优的,所以它们避免了最坏的情况-Quicksort场景),O(log n)额外空间,它们至少保留了Quicksort关于已经排序的数据集的良好行为的“一半”。 Dikert和Weiss在http://arxiv.org/pdf/1209.4214v1.pdf中提出了一个非常有趣的算法:

  • 选择一个枢轴p作为sqrt(n)元素的随机样本的中位数(这可以通过Tarjan&amp; co的算法在最多24 sqrt(n)比较中完成,或者通过5 sqrt(n)比较通过更为复杂的Schonhage蜘蛛工厂算法;
  • 将数组分为两部分,如Quicksort的第一步;
  • Heapify最小的部分并使用O(log n)额外位来编码堆,其中每个左子的值都大于其兄弟;
  • 递归地提取堆的根,向下移动根留下的lacune直到它到达堆的叶子,然后用从阵列的其他部分取出的适当元素填充lacune;
  • 重复数组的剩余非有序部分(如果选择p作为确切的中位数,则根本没有递归)。

答案 4 :(得分:2)

小样。在quick sortmerge sort之间,因为两者都是就地排序的类型,所以对于快速排序的wrost case运行时间的wrost case运行时间之间存在差异O(n^2)并且对于堆排序它是仍然O(n*log(n)),对于平均数据量,快速排序会更有用。由于它是随机算法所以得到正确的ans的概率。在更短的时间内将取决于您选择的枢轴元素的位置。

所以

好的电话: L和G的尺寸均小于3s / 4

通话不良: L和G中的一个大小超过3秒/ 4

对于少量我们可以进行插入排序,并且非常大量的数据用于堆排序。

答案 5 :(得分:2)

好吧,如果你去建筑层......我们在缓存内存中使用队列数据结构。那么队列中可用的东西将被排序。在快速排序中,我们没有问题将数组划分为任何长度......但是在堆排序中(通过使用数组)可能会发生这样的情况:父节点可能不存在于高速缓存中可用的子数组中,然后它必须将它带入高速缓冲存储器......这是非常耗时的。 快速排序是最好的!!

答案 6 :(得分:1)

Heapsort的好处是运行情况最糟糕的是 O(n * log(n)),因此在快速排序可能表现不佳的情况下(通常是大多数排序的数据集),heapsort是非常喜欢。

答案 7 :(得分:1)

Heapsort构建一个堆,然后重复提取最大项。最糟糕的情况是O(n log n)。

但如果您看到quick sort的最坏情况,即O(n2),您会意识到快速排序对于大数据来说是不太好的选择。

所以这使得排序是一件有趣的事情;我相信今天有这么多排序算法的原因是因为它们都是最好的地方“最好”。例如,如果对数据进行排序,冒泡排序可以执行快速排序。或者,如果我们对要排序的项目有所了解,那么我们可能会做得更好。

这可能不会直接回答你的问题,我想加上我的两分钱。

答案 8 :(得分:1)

在处理非常大的输入时,堆排序是一个安全的选择。渐近分析揭示了Heapsort在最坏情况下的增长顺序是Big-O(n logn),这比Quicksort的Big-O(n^2)更好。但是,Heapsort在大多数机器上实际上比实现快速排序要慢一些。 Heapsort也不是一个稳定的排序算法。

heapsort在实践中比quicksort慢的原因是由于quicksort中的引用(“https://en.wikipedia.org/wiki/Locality_of_reference”)更好的位置,其中数据元素位于相对较近的存储位置。具有强参考局部性的系统是性能优化的理想选择。然而,堆排序处理更大的跳跃。这使得quicksort更适合较小的输入。

答案 9 :(得分:1)

对我来说,heapsort和quicksort之间有一个非常根本的区别:后者使用递归。在递归算法中,堆随着递归的数量而增长。如果 n 很小,这无关紧要,但现在我正在用 n = 10 ^ 9 !!对两个矩阵进行排序。该程序需要大约10 GB的内存,任何额外的内存将使我的计算机开始交换到虚拟磁盘内存。我的磁盘是一个RAM磁盘,但仍然交换它会产生巨大的速度差异。所以在用C ++编写的包含可调维度矩阵的statpack中,程序员的大小未知,非参数统计类型的排序我更喜欢heapsort以避免延迟使用非常大的数据矩阵。

答案 10 :(得分:0)

简单地说>> HeapSort保证了〜O(n log n)的最坏情况下的运行时间,而不是QuickSort的 平均运行时间为“ O(n log n)”。在实践中通常使用QuickSort,因为通常它速度更快,但是 当您需要对不适合您内存的大文件进行排序时,HeapSort用于外部排序 电脑。

答案 11 :(得分:-1)

回答原始问题,并在此处解决其他一些评论:

我只是比较了选择,快速,合并和堆排序的实现,以了解它们如何相互叠加。答案是他们都有自己的缺点。

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深堆中的距离。堆可以很容易地在原始数组中就地实现,因此它不需要额外的内存,并且它是迭代的,因此在递归时不必担心堆栈溢出。堆排序的巨大的缺点是它不是一个稳定的排序,这意味着如果你需要它就会出现。