Почему четно-нечетное разделение «быстрее» для сортировки слиянием?

#algorithm #performance #big-o #mergesort

#алгоритм #Производительность #big-o #сортировка слиянием

Вопрос:

Сортировка слиянием — это алгоритм «разделяй и властвуй», который делит входные данные на несколько частей и решает части рекурсивно.

…Существует несколько подходов к функции разделения. Один из способов — разделить посередине. Этот подход обладает некоторыми приятными свойствами, однако мы сосредоточимся на методе, который немного быстрее: четно-нечетное разделение. Идея состоит в том, чтобы поместить каждый элемент с четной позицией в один список, а каждую нечетную позицию в другой.

Это прямо из моих конспектов лекций. Почему именно так происходит, что четно-нечетное разделение происходит быстрее, чем в середине массива?

Я предполагаю, что это как-то связано со списком, передаваемым в сортировку слиянием и имеющим качество уже отсортированного, но я не совсем уверен.

Кто-нибудь может пролить некоторый свет на это?

Редактировать: Я попытался запустить следующее на Python…

 global K
K = []
for i in range (1, 100000):
    K.append(i)


def testMergeSort():
"""
testMergeSort shows the proper functionality for the
Merge Sort Algorithm implemented above.
"""

t = Timer("mergeSort([K])", "from __main__ import *")
print(t.timeit(1000000))

p = Timer("mergeSort2([K])", "from __main__ import *")
print(p.timeit(1000000))
  

(Сортировка слиянием — это четно-нечетная сортировка слияния2, которая делится по центру)

И результатом было:

0.771506746608

0.843161219237

Комментарии:

1. Я пытаюсь выяснить, в чем преимущество при объединении отсортированных подсписков обратно вместе … может ли это быть связано с параллелизуемостью алгоритма?

2. Как насчет того, чтобы спросить в cstheory.stackexchange.com

3. Объединение двух отсортированных списков означает повторение списков один раз, поэтому для меня это выглядит как O (n). Не уверен, что слияние можно легко распараллелить. Что касается разделения, я понятия не имею, зачем выделять два вложенных списка, повторять список, перемещать значения в альтернативные списки, проверять, находится ли в конце списка и т.д. И т.п. Может считаться быстрее, чем ‘shr 1’ <g>.

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

5. @Не уверен, что в цитате из ваших заметок нигде не сказано «массив». Вы уверены, что не имеете дело со связанными списками, где преимущество очевидно?

Ответ №1:

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

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

Я должен добавить, что я не эксперт в этих вопросах, это просто то, что пришло на ум…

Комментарии:

1. Другими словами, четно-нечетное разделение может выполняться онлайн , а не оффлайн.

Ответ №2:

Чем ближе входной список к уже сортируемому, тем меньше время выполнения (это потому, что merge процедуре не нужно move вводить какие-либо значения, если все уже находится в правильном порядке; она просто выполняет O (n) сравнений. Поскольку сортировка слиянием рекурсивно вызывает себя для каждой половины разделения, требуется выбрать split функцию, которая увеличивает вероятность того, что результирующие половины списка находятся в отсортированном порядке. Если список в основном отсортирован, четно-нечетное разделение сделает это лучше, чем разделение посередине. Например,

 MergeSort([2, 1, 4, 3, 5, 7])
  

приведет к

 Merge(MergeSort([2, 1, 4]), MergeSort([3, 5, 7]))
  

если мы разделим посередине (обратите внимание, что оба вложенных списка имеют ошибки сортировки), тогда как если бы мы разделили четно-нечетное разделение, мы получили бы

 Merge(MergeSort([2, 4, 5]), MergeSort([1, 3, 7]))
  

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

Ответ №3:

Я подозреваю, что в вашем эксперименте есть шум. 🙂 Часть этого может быть вызвана тем, что compare-and-swap фактически не перемещает какие-либо элементы в списке, что позволяет избежать недействительности кэша и т.д.

Несмотря на это, здесь есть чат об этом: https://cstheory.stackexchange.com/questions/6732/why-is-an-even-odd-split-faster-for-mergesort/6764#6764 (и да, я опубликовал там аналогичный ответ (полное раскрытие))

В соответствующих статьях Википедии указывается, что сортировка слиянием равна O ( n log(n) ), в то время как сортировка слиянием по четным нечетам равна O (n log (n) ^ 2 ). Четно-нечетное, безусловно, «медленнее», но сеть сортировки статична, поэтому вы всегда знаете, какие операции собираетесь выполнить и (глядя на рисунок в статье Википедии) обратите внимание, что алгоритм остается параллельным до конца.

Когда сортировка слиянием, наконец, объединяет 2 списка вместе, последние сравнения сети сортировки из 8 элементов для сортировки слиянием по нечетно-четным числам все еще независимы.