2009-01-31 3 views
20

Вчера я работал над реализацией быстрой сортировки, а затем запускал ее, ожидая более быстрой работы, чем Mergesort (который я также реализовал). Я запустил два, и, хотя быстродействующая сортировка была быстрее для небольших наборов данных, 44 элемента (и я сделал, убедитесь, что он работает), mergesort стал более быстрым алгоритмом довольно быстро. Меня научили, что quicksort почти всегда «быстрее», чем слияние, и я понимаю, что есть некоторые дебаты по этой теме, но я, по крайней мере, ожидал, что это будет ближе, чем это. Для наборов данных> 10000 элементов слияние было более чем в 4 раза быстрее. Ожидается ли это, или есть ошибка в моем коде quicksort?Быстрый поиск медленнее, чем у Mergesort?

слияние:

public static void mergeSort(int[ ] e) 
{ 
    if (e.length <= 1) return; 
    int[] first = new int[e.length/2]; 
    int[] second = new int[e.length - first.length]; 
    System.arraycopy(e, 0, first, 0, first.length); 
    System.arraycopy(e, first.length, second, 0, second.length); 
    mergeSort(first); 
    mergeSort(second); 
    System.arraycopy(merge(first, second), 0, e, 0, e.length); 
} 

private static int[] merge(int[] first, int[] second) { 
    int iFirst = 0; 
    int iSecond = 0; 
    int iCombined = 0; 

    int[] combined = new int[first.length + second.length]; 
    while(iFirst < first.length && iSecond < second.length) { 
     if (first[iFirst] > second[iSecond]) { 
      combined[iCombined++] = second[iSecond++]; 
     } 
     else combined[iCombined++] = first[iFirst++]; 
    } 
    for(; iFirst < first.length; iFirst++) { 
     combined[iCombined++] = first[iFirst]; 
    } 
    for(; iSecond < second.length; iSecond++) { 
     combined[iCombined++] = second[iSecond]; 
    } 
    return combined; 
} 

быстрая сортировка:

public static void quicksort(int[] a, int first, int last) { 
    if (first >= last) return; 

    int partitionIndex = partition(a, first, last); 
    quicksort(a, first, partitionIndex - 1); 
    quicksort(a, partitionIndex + 1, last); 
} 

public static int partition(int[] x, int first, int last) { 
    int left = first; 
    int right = last; 
    int pivot = x[first]; 
    int pivotIdx = first; 

    while(left <= right) { 
     while(left < x.length && x[left] <= pivot) left++; 
     while(right >= 0 && x[right] > pivot) right--; 
     if (left <= right) { 
      int temp = x[left]; 
      x[left] = x[right]; 
      x[right] = temp; 
     } 
    } 
    pivotIdx = right; 
    x[first] = x[right]; 
    x[pivotIdx] = pivot; 
    return pivotIdx; 
} 

ответ

2

На основе этой википедии article, как ожидается, ваши результаты.

+1

Нет, это не так. Быстрая сортировка без ошибок выполняется быстрее. –

+0

@Stephan Eggermont: Можете ли вы указать на ошибки в реализации Джона? – Giorgio

+0

см. Мой ответ выше –

1

Я мог представить себе, что, используя прямой доступ к памяти, используя, например, C, можно улучшить производительность Quicksort больше, чем это возможно с помощью Mergesort.

Другая причина в том, что для Mergesort требуется больше памяти, потому что трудно реализовать ее как место на месте.

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

Как можно видеть, on wikipedia, можно реализовать Quicksort по-разному.

0

Были ли у вас наборы данных достаточно случайными? Были ли они частично отсортированы?

Это может повлиять на скорость рода ...

Как для раздела QuickSort в(), вы бы пропустить вместе, если цифры в отсортированном порядке, пока вы не найдете, что это не так.

0

Это может зависеть от того, какие данные вы сортируете для тестирования (уже упорядоченные списки, рандомизированные, обратные сортировки). Кроме того, quicksort, вероятно, будет быстрее вообще, если вы выберете случайный стержень вместо использования первого элемента.

2

Худший случай сортировки сортировки - это средний случай quicksort, поэтому, если у вас нет хорошей реализации, сортировка слияния будет быстрее в целом. Быстрое ускорение работы заключается в том, чтобы избегать случаев со средними показателями. Выберите лучший стержень (медианный из 3 помогает), и вы увидите разницу.

+0

Я не понимаю аргументации. Если quicksort - это O (n log (n)) _ в среднем, это связано с тем, что существуют суб-средние случаи, и вы не можете избежать их, независимо от того, как вы выбираете свой стержень. Или я что-то пропускаю? – Giorgio

3

Одним из преимуществ быстрой сортировки для относительно небольших размеров массива является просто артефакт реализации оборудования.

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

В результате, для относительно небольших наборов данных, quicksort, скорее всего, получит кеш-хиты и, следовательно, имеет тенденцию работать быстрее на большинстве аппаратных средств.

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

10

Я на самом деле просто написал «пробную программу сравнения сравнительного списка» в C и пришел к аналогичному выводу (что mergesort будет бить quicksort для большинства применений), хотя мне сказали, что quicksort обычно не используется для связанных списки в любом случае. Я хотел бы отметить, что выбора оси значений является фактором монстра - моя первая версия используется случайный узел как стержень, и когда я уточнена его немного, чтобы взять среднее из двух (случайных) узлов, время exectution для 1000000 записей перешло от более 4 минут до менее 10 секунд, положив их прямо наравне с mergesort.

Mergesort и quicksort имеют одинаковый большой размер (n * log (n)) и, несмотря на то, что люди могут попытаться потребовать, большой O действительно относится к количеству итераций, а не к количеству сравнения. Основная разница , которая может быть произведена между двумя из них, всегда будет связана с бесполезной сортировкой, и она включает списки, которые уже в значительной степени отсортированы или содержат большое количество связей (когда quicksort делает лучше, чем mergesort, разница не будет почти такой большой). Это связано с тем, что привязки или уже отсортированные сегменты упорядочиваются прямо через mergesort; когда два разделенных списка возвращаются для объединения, если один список уже содержит все меньшие значения, все значения слева будут сравниваться по одному с первым элементом справа, а затем (поскольку возвращенные списки имеют внутренний заказ). Дальше сравнения необходимо сделать, а справа - итерационный на конце. Это означает, что количество итераций останется неизменным, но количество сравнений сокращается наполовину. Если вы говорите об актуальном времени и сортируете строки, это сравнение стоит дорого.

Связи и уже отсортированные сегменты в quicksort могут легко привести к несбалансированным спискам, если значение поворота не определено тщательно, а несбалансированные списки (например, один справа, десять слева) являются причиной замедления. Таким образом, если вы можете получить свой quicksort для выполнения также в уже отсортированном списке, как это делается в ramdomized list, у вас есть хороший метод для поиска точки.

Если вы заинтересованы, демонстрационная программа производит вывод так:

[root~/C] ./a.out -1 3 
Using "", 0 records 
Primary Criteria offset=128 

Command (h for help, Q to quit): N 
How many records? 4000000 
New list is 562500.00 kb 

Command (h for help, Q to quit): m 

Mergesorting..............3999999 function calls 
123539969 Iterations  Comparison calls: 82696100 
Elapsed time: 0 min 9 sec 


Command (h for help, Q to quit): S 
Shuffled. 

Command (h for help, Q to quit): q 

Quicksorting..............4000000 function calls 
190179315 Iterations  Comparison calls: 100817020 
Elapsed time: 0 min 23 sec 

Altho без Krazy Kolors. У меня есть кое-что еще около половины this page.

ps. ни один сорт не требует дополнительной памяти со связанным списком.

+1

Это нерелевантный ответ, поскольку он использует резервное хранилище связанных списков. –

+0

Вы сказали, что «Mergesort и quicksort имеют один и тот же самый большой случай O (n * log (n))», но я хочу упомянуть, что Big O строго для верхнего ограничения времени выполнения (это только худший случай). Большая Омега описывает нижнюю границу (лучший случай). – talloaktrees

1

(1) Существует qsort algo, используемый C qsort(), который не требует дополнительной памяти. Этот был, скорее всего, изобретен Хоар. Это делает qsort() быстро в C.

(2) Рандомизация данных перед запуском qsort почти всегда ускорит его.

(3) выбор медианные данные для поворота могут сделать это быстрее,

+0

Даже если это называется qsort(), это, вероятно, не чистый быстрый вид. – Giorgio

1

Это согласуется с анализом алгоритмов. Merge-sort гарантированно O (nlogn) для любого ввода и для каждого времени выполнения. Quicksort - наилучший случай O (nlogn) и средний случай O (nlogn), но наихудший случай O (n^2), поэтому среднее выполнение будет находиться между O (nlogn) и O (n^2).

Quicksort - лучший алгоритм общего случая, поскольку он имеет низкие накладные расходы, поэтому он имеет хорошую скорость для значений n до примерно 10000 или около того и все еще хорошее время выполнения для произвольно астрономических значений n. У слияния-сортировки есть несчастливые накладные расходы на запись фрейма стека, требуемого каждым рекурсивным вызовом. Таким образом, при малых значениях n он имеет ужасно высокий c в RT = cnlogn и не является предпочтительным общим методом сортировки.

Редактировать: Программное обеспечение Обезьяна указала на противоречие: средние средние значения O (nlogn) для случайного ввода, но O (n^2) наихудший случай. Таким образом, это на самом деле несколько связано энтропией ваших данных - или вы можете выбрать шарнир случайным образом. Хотя я все еще могу быть немного.

+0

Quicksort не может быть «средним случаем O (nlogn)» и «средним ... между O (nlogn) и O (n^2)». –

+0

Извините, что средний O (nlogn) для случайного ввода, но O (n^2) наихудший случай Так что он фактически несколько связан энтропией – Overflown

0

Для хорошей работы сортировки, важен не рекурсия всего пути вниз к спискам длина 1

Вы должны рассмотреть сортировку списков 2, 3 и даже 4 как вложенный МФС поменяв при необходимости. Сообщите нам, как меняется производительность.

1

Если вы выполняете сортировку кучи в качестве базового алгоритма сортировки в сценарии наихудшего сценария быстрого сортировки, вы получаете алгоритм theta (n log n).

Если вам не нужна стабильная сортировка и не сортировать связанный список, я думаю, что это было бы самое быстрое, что вы могли бы сделать.

Merge sort

4

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

  • qsort the shortest subarray first.
  • переключатель для включения сортировки ниже 5-25 элементов
  • сделать нормальный выбор поворота

Ваш QSort очень медленно, потому что он пытается разделить и QSort массивы длины 2 и 3.

+1

+1 Для переключения в сортировку вставки должно получиться приятное улучшение – helpermethod

+1

Любая причина, по которой вы предложить оптимизацию быстрой реализации сортировки, а не реализацию сортировки слияния? Сортировка слияния также может пригодиться при переключении на сортировку вставки (см. Пример timsort). Кстати, многие реализации языка программирования используют оптимизированную версию сортировки слияния внутри: Java, Python, C с GNU libc ... Позднее даже вызывает быстрый сортировать «медленный алгоритм». –

1

I выполняли аналогичные тесты и чистую быструю сортировку (со случайным выбором поворота) оказались намного медленнее, чем сортировка слияния для больших массивов.

Выбор пивота как медиана первого, среднего и последнего элемент улучшена производительность быстро sort`s, но быстрая сортировка была еще определенно хуже, чем сортировка слияния на больших массивах (> 100000 элементов).

Я видел большое улучшение, когда я реализовал интро-сортировку, то есть быструю сортировку, которая возвращается к сортировке кучи, если глубина рекурсии превышает определенный порог. Моя реализация встроенного сортировки была почти такой же быстрой, как и реализация сортировки слиянием. Конечно, intro-sort больше не является чистым быстрым сортировкой, поскольку он использует сортировку кучи, чтобы вернуть сложность к n log (n), когда чистая быстрая сортировка удаляет некоторые плохие данные. Я могу опубликовать результаты, если вы заинтересованы.

1

Я думаю, что до тех пор, пока данные вписываются в память, хорошая реализация сортировки слияния выполняется лучше, чем хорошая быстрая сортировка.

Одна из наиболее широко используемых реализаций qsort(), glibc qsort(), внутренне использует сортировку слияния для большинства случаев, когда данные вписываются в память.Эта сортировка слияния выделяет временное пространство памяти, используемое для слияния, что добавляет некоторые издержки памяти, но большую часть времени оно превосходит собственную внутреннюю реализацию быстрой сортировки с хорошим выбором и оптимизацией свода. glibc использует только quicksort, когда данные и временная память для сортировки слияния не могут поместиться в память.

Я измерил производительность этих двух реализаций на своей машине с 2,1 ГГц процессором с несколькими ГБ ОЗУ. Входы генерируются с псевдослучайным генератором, и каждая клавиша представляет собой 32-битное беззнаковое целое число, что означает бит больше циклов сравнения, чем целочисленное сравнение из-за интерфейса функции сравнения.

Для сортировки слиянием:

2 MB, time_diff 165.156000 ms, 78.752518 ns per byte 
4 MB, time_diff 344.298000 ms, 82.087040 ns per byte 
8 MB, time_diff 730.926000 ms, 87.133169 ns per byte 
16 MB, time_diff 1541.215000 ms, 91.863573 ns per byte 
32 MB, time_diff 3088.924000 ms, 92.057109 ns per byte 
64 MB, time_diff 6262.868000 ms, 93.324006 ns per byte 
128 MB, time_diff 12887.018000 ms, 96.015766 ns per byte 
256 MB, time_diff 26731.597000 ms, 99.582959 ns per byte 

Для быстрой сортировки:

2 MB, time_diff 243.519000 ms, 116.118908 ns per byte 
4 MB, time_diff 504.975000 ms, 120.395422 ns per byte 
8 MB, time_diff 1075.276000 ms, 128.182888 ns per byte 
16 MB, time_diff 2183.865000 ms, 130.168498 ns per byte 
32 MB, time_diff 4343.993000 ms, 129.461080 ns per byte 
64 MB, time_diff 8714.166000 ms, 129.851192 ns per byte 
128 MB, time_diff 17881.344000 ms, 133.226395 ns per byte 
256 MB, time_diff 36751.029000 ms, 136.908252 ns per byte 

Вы можете видеть, что существуют четкие различия в производительности между этими двумя реализация и почему слиянием предпочтительнее сортировки в таких широко используется реализация qsort. Основная причина этой разницы, по-видимому, заключается в том, что быстрая сортировка на 10-20% больше сравнений, чем сортировка слияния, из-за неравномерного разделения на каждом шаге.

Смежные вопросы