Подтвердить что ты не робот

Быстрое сокращение медленнее, чем Mergesort?

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

слияние:

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;
}
4b9b3361

Ответ 1

Я на самом деле просто написал "пробную программу сравнения сравнительного списка" в C и пришел к аналогичному выводу (что mergesort будет бить quicksort для большинства применений), хотя мне сказали, что quicksort обычно не используется для связанных списков так или иначе. Я хотел бы отметить, что выбор значений поворота является фактором монстра. В моей начальной версии использовался случайный node как опорный элемент, и когда я немного уточнил его, чтобы взять среднее из двух (случайных), время выключения для 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 без крази-колор. Там есть еще несколько вещей о нем, примерно на полпути this page.

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

Ответ 2

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

  • Сначала введите самый младший субарак.
  • переключатель для сортировки вставки ниже 5-25 элементов
  • сделать обычный выбор точки поворота

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

Ответ 4

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

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

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

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

Ответ 6

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

Ответ 7

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

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

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

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

Ответ 8

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

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

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

Ответ 9

Это согласуется с анализом алгоритмов. 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) наихудший случай. Таким образом, это на самом деле несколько связано энтропией ваших данных - или вы можете выбрать шарнир случайным образом. Я все еще могу быть немного, хотя.

Ответ 10

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

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

Объединить сортировку

Ответ 11

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

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

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

Ответ 12

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

Одна из наиболее широко используемых реализаций 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

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

Ответ 13

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

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

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

Ответ 14

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

Ответ 15

Для хорошей производительности quicksort важно не переписывать весь путь до списков длины 1

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