Куча сортировки имеет худшую сложность O(nlogn)
, в то время как Quicksort имеет O(n^2)
.
Но эмпирические доказательства говорят, что quicksort превосходит. Почему это?
Быстрое превосходство над кучкой Sort
Ответ 1
Одним из основных факторов является то, что у quicksort есть лучшая локальность ссылок - следующая вещь, к которой нужно получить доступ, обычно закрывается в памяти, к чему вы только что посмотрели. Напротив, гепсорт скачет значительно больше. Поскольку вещи, которые находятся близко друг к другу, скорее всего, будут кэшироваться вместе, quicksort имеет тенденцию быть быстрее.
Однако производительность худшего производителя в худшем случае значительно хуже, чем у heapsort. Поскольку для некоторых критически важных приложений требуются гарантии скорости, heapsort - это правильный путь для таких случаев.
Ответ 2
Heapsort O (N log N) гарантирован, что намного лучше, чем худший случай в Quicksort. Heapsort не нуждается в большем количестве памяти для другого массива для размещения упорядоченных данных, как это требуется Mergesort. Итак, почему коммерческие приложения придерживаются Quicksort? Что такое Quicksort, что особенно важно для других реализаций?
Я сам тестировал алгоритмы, и я видел, что у Quicksort есть что-то особенное. Он работает быстро, намного быстрее, чем алгоритмы кучи и слияния.
Секрет Quicksort: он почти не делает ненужных свопов элементов. Своп занимает много времени.
С помощью Heapsort, даже если все ваши данные уже заказаны, вы собираетесь поменять 100% элементов для заказа массива.
С Mergesort это еще хуже. Вы собираетесь написать 100% элементов в другом массиве и записать его обратно в исходное, даже если данные уже упорядочены.
С Quicksort вы не поменяете то, что уже заказано. Если ваши данные полностью заказаны, вы почти ничего не меняете! Несмотря на то, что в худшем случае много шума, небольшое улучшение в выборе стержня, любое другое, чем получение первого или последнего элемента массива, может избежать этого. Если вы получаете опорный элемент промежуточного элемента между первым, последним и средним элементом, достаточно избегать худшего случая.
Что лучше в Quicksort - это не худший случай, но лучший случай! В лучшем случае вы делаете такое же количество сравнений, хорошо, но вы почти ничего не меняете. В среднем случае вы меняете часть элементов, но не все элементы, как в 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
Вот пара объяснений:
http://www.cs.auckland.ac.nz/software/AlgAnim/qsort3.html
http://users.aims.ac.za/~mackay/sorting/sorting.html
По сути, хотя наихудший случай быстрого сортировки - O (n ^ 2), он в среднем будет работать лучше.: -)
Ответ 4
Знак большой O означает, что время, необходимое для сортировки n элементов, ограничено сверху функцией c*n*log(n)
, где c
- некоторый неопределенный постоянный коэффициент. Нет причин, по которым константа c
должна быть одинаковой для quicksort
и heapsort
. Итак, реальный вопрос: почему вы ожидаете, что они будут одинаково быстрыми?
quicksort
на практике всегда был несколько быстрее, чем heapsort
, но в последнее время разница стала больше, поскольку, как упоминалось ранее, локальность доступа к памяти стала настолько важной для скорости выполнения.
Ответ 5
Сложность в среднем случае и тот факт, что вы можете предпринять простые шаги для минимизации риска наихудшего случая в Quicksort (например, выберите опорный стержень как медиану из трех элементов, а не одну выбранную позицию).