Quicksort против Heapsort - PullRequest
       44

Quicksort против Heapsort

74 голосов
/ 18 марта 2010

И быстрая сортировка, и heapsort выполняют сортировку на месте. Что лучше? В каких случаях и в каких случаях они предпочтительнее?

Ответы [ 11 ]

86 голосов
/ 15 февраля 2015

Heapsort гарантированно O (N log N), что намного лучше, чем наихудший случай в быстрой сортировке. Heapsort не требует больше памяти для другого массива для размещения упорядоченных данных, как это требуется Mergesort. Так почему же коммерческие приложения придерживаются Quicksort? Какая у Quicksort такая особенность по сравнению с другими реализациями?

Я сам протестировал алгоритмы и увидел, что в Quicksort действительно есть что-то особенное. Он работает быстро, намного быстрее, чем алгоритмы Heap и Merge.

Секрет быстрой сортировки заключается в следующем: он почти не делает ненужных перестановок элементов. Своп требует много времени.

С Heapsort, даже если все ваши данные уже упорядочены, вы собираетесь поменять 100% элементов, чтобы упорядочить массив.

С Mergesort это еще хуже. Вы собираетесь записать 100% элементов в другой массив и записать его обратно в исходный, даже если данные уже упорядочены.

С помощью быстрой сортировки вы не меняете то, что уже заказано. Если ваши данные полностью упорядочены, вы почти ничего не поменяете! Хотя в худшем случае много шума из-за небольшого улучшения выбора pivot, кроме получения первого или последнего элемента массива, его можно избежать. Если вы получаете опору от промежуточного элемента между первым, последним и средним элементом, достаточно избежать наихудшего случая.

То, что превосходит 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);
}
46 голосов
/ 18 марта 2010

Этот документ имеет некоторый анализ.

Также из Википедии:

Самый прямой конкурент быстрая сортировка Heapsort есть как правило, немного медленнее, чем быстрая сортировка, но в худшем случае время всегда Θ (нлогн). Быстрая сортировка обычно быстрее, хотя остается шанс худшего варианта кроме как в варианте интросорт, который переключается на heapsort, когда плохой случай обнаружен. Если это известно заранее этот кучи будет необходимо, используя его напрямую будет быстрее, чем ждать интросорта переключиться на него.

14 голосов
/ 03 октября 2011

Для большинства ситуаций иметь быстрое против немного более быстрого не имеет значения ... Вы просто никогда не хотите, чтобы это иногда приводило к медленному ожиданию. Хотя вы можете настроить QuickSort, чтобы избежать медленных ситуаций, вы теряете элегантность базовой QuickSort. Так что, для большинства вещей я на самом деле предпочитаю HeapSort ... вы можете реализовать его в полной простоте и никогда не получать медленную сортировку.

Для ситуаций, когда вы действительно хотите получить максимальную скорость в большинстве случаев, быстрая сортировка может быть предпочтительнее, чем HeapSort, но ни один из них не может быть правильным ответом. В критических для скорости ситуациях стоит внимательно изучить детали ситуации. Например, в некоторых моих критических по скорости кодах очень часто данные уже отсортированы или почти отсортированы (это индексирование нескольких связанных полей, которые часто либо перемещаются вверх и вниз вместе, либо перемещаются вверх и вниз друг против друга, поэтому, когда вы сортируете по одному, остальные сортируются или сортируются в обратном порядке или закрываются ... и то, и другое может убить быструю сортировку). Для этого случая я не реализовал ни одного ... вместо этого я реализовал Dijkstra's SmoothSort ... вариант HeapSort, который является O (N), когда уже отсортирован или почти отсортирован ... это не так элегантно, не слишком легко понять, но быстро ... прочитайте http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF, если вы хотите что-то более сложное в коде.

5 голосов
/ 21 января 2013

Гибриды на месте Quicksort-Heapsort также действительно интересны, поскольку большинству из них требуется только n * log n сравнений в худшем случае (они оптимальны по отношению к первому члену асимптотики, поэтому они избегают худшего -сценарии Quicksort), O (log n) дополнительного пространства, и они сохраняют как минимум «половину» хорошего поведения Quicksort по отношению к уже упорядоченному набору данных. Чрезвычайно интересный алгоритм представлен Дикертом и Вейссом в http://arxiv.org/pdf/1209.4214v1.pdf:

  • Выберите опору p в качестве медианы случайной выборки элементов sqrt (n) (это можно сделать не более чем за 24 сравнения sqrt (n) с помощью алгоритма Tarjan & co, или сравнения 5 sqrt (n) с более замысловатый паук-фабричный алгоритм Schonhage);
  • Разделите ваш массив на две части, как на первом шаге быстрой сортировки;
  • Куча наименьшей части и использование O (log n) дополнительных битов для кодирования кучи, в которой у каждого левого потомка есть значение больше, чем у его родного брата;
  • Рекурсивно извлечь корень кучи, просеять лакуну, оставленную корнем, пока она не достигнет листа кучи, затем заполнить лакуну соответствующим элементом, взятым из другой части массива;
  • Повторение по оставшейся неупорядоченной части массива (если p выбрано в качестве точной медианы, рекурсия вообще отсутствует).
2 голосов
/ 21 мая 2016

Хорошо, если вы переходите на уровень архитектуры ... мы используем структуру данных очереди в кэш-памяти. Так что все, что доступно в очереди, будет отсортировано. Как и в быстрой сортировке, у нас нет проблем с разделением массива на любую длину ... но при сортировке кучи (с использованием массива) может так случиться, что родительский элемент может отсутствовать в подмассиве, доступном в кеше, и затем он должен перенести его в кеш-память ... что отнимает много времени. Это быстрая сортировка лучше !! !!

2 голосов
/ 26 сентября 2012

Comp. между quick sort и merge sort, поскольку обе являются типом сортировки на месте, существует разница между временем выполнения Wrost и WR для быстрой сортировки O(n^2), а для сортировки в куче - все еще O(n*log(n)) средний объем данных быстрой сортировки будет более полезным. Так как это рандомизированный алгоритм, то вероятность получения правильного ответа. в меньшее время будет зависеть от позиции элемента поворота вы выбираете.

Итак,

Хороший звонок: размеры L и G меньше 3 с / 4

Неправильный вызов: один из L и G имеет размер больше 3 с / 4

для небольшого количества мы можем перейти на сортировку вставкой, а для очень большого количества данных - на сортировку кучи.

1 голос
/ 16 апреля 2016

Для меня есть очень фундаментальное различие между heapsort и quicksort: последний использует рекурсию. В рекурсивных алгоритмах куча растет с количеством рекурсий. Это не имеет значения, если n мало, но сейчас я сортирую две матрицы с n = 10 ^ 9 !!. Программа занимает почти 10 ГБ оперативной памяти, и любая дополнительная память заставит мой компьютер начать подкачку к памяти виртуального диска. Мой диск - это RAM-диск, но при его замене получается огромная разница в скорости . Поэтому в statpack, написанном на C ++, который включает в себя матрицы регулируемых размеров, размер которых неизвестен программисту заранее, и непараметрический статистический тип сортировки, я предпочитаю, чтобы во избежание задержек использовались матрицы с очень большими матрицами.

1 голос
/ 26 ноября 2015

Сортировка кучи - безопасная ставка при работе с очень большими входами. Асимптотический анализ показывает, что порядок роста Heapsort в худшем случае составляет Big-O(n logn), что лучше, чем Big-O(n^2) Quicksort в худшем случае. Однако на большинстве машин Heapsort несколько медленнее, чем хорошо реализованная быстрая сортировка. Heapsort также не является стабильным алгоритмом сортировки.

Причина, по которой heapsort медленнее на практике, чем быстрой сортировкой, связана с лучшей локализацией ссылок ("https://en.wikipedia.org/wiki/Locality_of_reference") в быстрой сортировке, где элементы данных находятся в относительно близких местах хранения. Системы, которые демонстрируют сильную локализацию ссылок, являются большими Кандидаты на оптимизацию производительности. Однако сортировка кучи имеет дело с большими скачками. Это делает быструю сортировку более благоприятной для меньших входных данных.

1 голос
/ 18 марта 2010

Heapsort строит кучу, а затем многократно извлекает максимальный предмет. Худший случай - O (n log n).

Но если бы вы увидели худший случай быстрой сортировки , то есть O (n2), вы бы поняли, что быстрая сортировка была бы не очень хорошим выбором для больших данных.

Так что это делает сортировку интересной вещью; Я считаю, что причина, по которой сегодня так много алгоритмов сортировки, состоит в том, что все они «лучшие» в своих лучших местах. Например, пузырьковая сортировка может выполнить быструю сортировку, если данные отсортированы. Или, если мы знаем что-то о сортируемых товарах, то, вероятно, мы сможем добиться большего.

Возможно, это не ответит на ваш вопрос напрямую, хотя я бы добавил свои два цента.

1 голос
/ 18 марта 2010

Heapsort имеет преимущество в том, что наихудший рабочий случай составляет O (n * log (n)) , поэтому в случаях, когда быстрая сортировка может работать плохо (в основном отсортированные наборы данных в целом), heapsort значительно предпочтительным.

...