Как quicksort, так и heapsort делают сортировку на месте. Что лучше? Каковы приложения и случаи, в которых это предпочтение?
Quicksort vs heapsort
Ответ 1
Эта статья имеет некоторый анализ.
Также из Википедии:
Самый прямой конкурент быстрой сортировки - heapsort. Heapsort, как правило, несколько медленнее, чем quicksort, но наихудшее время выполнения всегда Θ (nlogn). Быстрая сортировка обычно выполняется быстрее, хотя сохраняется вероятность наихудшей производительности, за исключением варианта с внутренней сортировкой, который переключается на динамическую сортировку при обнаружении плохого случая. Если заранее известно, что heapsort будет необходим, его непосредственное использование будет быстрее, чем ожидание переключения на него.
Ответ 2
Heapsort гарантированно O (N log N), что намного лучше, чем худший случай в Quicksort. Heapsort не требует больше памяти для другого массива для размещения упорядоченных данных, как это требуется Mergesort. Так почему же коммерческие приложения придерживаются Quicksort? Какая у Quicksort такая особенность по сравнению с другими реализациями?
Я сам проверил алгоритмы и увидел, что в Quicksort действительно есть что-то особенное. Он работает быстро, намного быстрее, чем алгоритмы Heap и Merge.
Секрет быстрой сортировки заключается в следующем: он почти не делает ненужных перестановок элементов. Своп требует много времени.
С Heapsort, даже если все ваши данные уже упорядочены, вы собираетесь поменять 100% элементов, чтобы упорядочить массив.
С Mergesort все еще хуже. Вы собираетесь записать 100% элементов в другой массив и записать его обратно в исходный, даже если данные уже упорядочены.
С Quicksort вы не меняете то, что уже заказано. Если ваши данные полностью упорядочены, вы почти ничего не поменяете! Хотя в худшем случае много шума из-за небольшого улучшения выбора pivot, кроме получения первого или последнего элемента массива, его можно избежать. Если вы получаете поворот от промежуточного элемента между первым, последним и средним элементом, достаточно избежать наихудшего случая.
Что лучше в быстрой сортировке, это не худший случай, но лучший случай! В лучшем случае вы делаете такое же количество сравнений, хорошо, но вы почти ничего не меняете. В среднем случае вы меняете часть элементов, но не все элементы, как в Heapsort и Mergesort. Вот что дает Quicksort лучшее время. Меньше своп, больше скорости.
Приведенная ниже реализация в С# на моем компьютере, работающая в режиме выпуска, превосходит 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);
}
Ответ 3
В большинстве ситуаций быстрое или малое ускорение не имеет значения... вы просто никогда не хотите, чтобы это время от времени замедлялось. Хотя вы можете настроить QuickSort, чтобы избежать медленных ситуаций, вы теряете элегантность базового QuickSort. Поэтому для большинства вещей я предпочитаю HeapSort... вы можете реализовать его в своей простой простой элегантности и никогда не получать медленный вид.
В ситуациях, когда вам нужна максимальная скорость в большинстве случаев, QuickSort может быть предпочтительнее HeapSort, но ни один из них не может быть правильным ответом. Для критически важных ситуаций стоит внимательно изучить детали ситуации. Например, в некоторых из моего критически важного кода очень часто происходит сортировка или сортировка данных (это индексирование нескольких связанных полей, которые часто либо перемещаются вверх, так и вниз вместе или перемещаются вверх и вниз друг против друга, поэтому, как только вы сортируете по одному, остальные сортируются или сортируются в обратном порядке или закрываются... любой из них может убить QuickSort). В этом случае я не реализовал ни... вместо этого, я реализовал Dijkstra SmoothSort... вариант HeapSort, который является O (N), когда он уже отсортирован или почти отсортирован... он не настолько изящный, не слишком простой для понимания, но быстро... читать http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF, если вам нужно что-то более сложное для кода.
Ответ 4
Гибриды на месте 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 выбрано в качестве точной медианы, рекурсии вообще нет).
Ответ 5
Комп. между quick sort
и merge sort
, так как оба являются сортировкой по месту, существует разность между временем работы фростного времени в режиме времени ожидания wrost для быстрой сортировки O(n^2)
, а для сортировки кучи все еще O(n*log(n))
и для средний объем быстрой сортировки данных будет более полезен. Так как это рандомизированный алгоритм, так и вероятность получения правильных ans. за меньшее время будет зависеть от позиции выбранного элемента поворота.
Итак, <
Хороший вызов: размеры L и G кажутся меньше 3s/4
Плохой вызов: один из L и G имеет размер больше 3s/4
за небольшую сумму мы можем пойти для сортировки вставки и для очень большого количества данных перейти на сортировку кучи.
Ответ 6
Хорошо, если вы перейдете на уровень архитектуры... мы используем структуру данных очереди в кэш-памяти. То, что когда-либо доступно в очереди, будет отсортировано. Как в быстром роде у нас нет проблемы с делением массива на любую длину... но в сортировке кучи (с помощью массива) может случиться так, что родительский элемент может отсутствовать в дополнительном массиве, доступном в кеше, а затем он должен привести его в кэш-память... что требует много времени. Лучшая скорость! 😀
Ответ 7
У Heapsort есть преимущество в том, что у O (n * log (n) есть худший вариант работы), поэтому в случаях, когда quicksort, вероятно, будет работать плохо (в основном отсортированные наборы данных вообще), heapsort гораздо предпочтительнее.
Ответ 8
Heapsort создает кучу, а затем повторно извлекает максимальный элемент. Его худшим случаем является O (n log n).
Но если вы увидите худший случай quick sort, который является O (n2), вы бы поняли, что быстрая сортировка будет не очень хороший выбор для больших данных.
Таким образом, сортировка - интересная вещь; Я считаю, что причина, по которой многие алгоритмы сортировки живут сегодня, состоит в том, что все они являются "лучшими" в своих лучших местах. Например, сортировка пузырьков может выполнять быстрый сортировку, если данные сортируются. Или, если мы знаем что-то об элементах, которые нужно отсортировать, то, вероятно, мы сможем сделать лучше.
Это может не ответить на ваш вопрос напрямую, подумал, что я добавлю свои два цента.
Ответ 9
"Куча сортировки" - это безопасная ставка при работе с очень большими входами. Асимптотический анализ показывает, что порядок роста Heapsort в худшем случае составляет Big-O(n logn)
, что лучше, чем Quicksort Big-O(n^2)
в худшем случае. Тем не менее, Heapsort на большинстве машин несколько медленнее, чем на хорошо реализованной быстрой сортировке. Heapsort также не является стабильным алгоритмом сортировки.
Причина, по которой хапсорт на практике медленнее, чем quicksort, объясняется лучшей локальностью ссылки ( " https://en.wikipedia.org/wiki/Locality_of_reference" ) в quicksort, где элементы данных находятся в относительно близких местах хранения. Системы, которые демонстрируют высокую локальность ссылок, являются отличными кандидатами для оптимизации производительности. Однако сортировка кучи имеет дело с большими скачками. Это делает quicksort более благоприятным для небольших входов.
Ответ 10
Для меня существует очень принципиальное различие между heapsort и quicksort: последний использует рекурсию. В рекурсивных алгоритмах куча растет с количеством рекурсий. Это не имеет значения, если n мало, но сейчас я сортирую две матрицы с n = 10 ^ 9!!. Программа занимает почти 10 ГБ оперативной памяти, и любая дополнительная память заставит мой компьютер начать замену на виртуальную память. Мой диск - это RAM-диск, но при этом он меняет его на огромную разницу в скорости. Таким образом, в statpack, закодированном на С++, который включает в себя настраиваемые матрицы размеров, с заранее неизвестным размером до программиста и непараметрический статистический вид сортировки, я предпочитаю, чтобы heapsort избегал задержек использования с очень большими матрицами данных.
Ответ 11
Чтобы ответить на исходный вопрос и указать некоторые другие комментарии здесь:
Я просто сравнивал варианты выбора, быстрого, слияния и кучи, чтобы посмотреть, как они складываются друг против друга. Ответ заключается в том, что все они имеют свои недостатки.
TL; ДР: Быстрый - это лучший тип общего назначения (достаточно быстрый, стабильный и в основном на месте) Лично я предпочитаю сортировку кучи, хотя мне нужен стабильный вид.
Выбор - 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. Куча может быть легко реализована на месте в исходном массиве, поэтому для нее не требуется дополнительной памяти, и она повторяется, поэтому не стоит беспокоиться о переполнении стека во время рекурсии. Огромный недостаток в сортировке кучи состоит в том, что он не является стабильным видом, а это значит, что это нужно, если вам это нужно.