Почему для MergeSort используется "четкое" разделение "быстрее"?

MergeSort - это алгоритм разделения и покоя, который делит вход на несколько частей и рекурсивно решает реплики.

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

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

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

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

Изменить: я попытался выполнить следующее в 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))

(MergeSort является четным MergeSort, MergeSort2 делит вниз по центру)

И результат:

  

+0,771506746608

         

+0,843161219237

  

Ответ 1

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

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

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

Ответ 2

Чем ближе список ввода к уже отсортированному, тем ниже время выполнения (это связано с тем, что процедура merge не имеет значения move, если все уже в правильном порядке, а просто выполняет сравнения O (n). Так как MergeSort рекурсивно вызывает себя на каждую половину разделения, нужно выбрать функцию 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

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

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

В связанных статьях Википедии указывается, что mergesort - это O (n log (n)), а Odd-Even Merge Sort - O (n log (n) ^ 2). Odd-Even, безусловно, "медленнее", но сортировочная сеть является статической, поэтому вы всегда знаете, какие операции вы собираетесь выполнять, и (глядя на графику в записи в Википедии) обратите внимание на то, как алгоритм остается параллельным до конца.

В случае, когда сортировка слияния, наконец, объединяет 2 списка, последние сравнения 8-элементной сортировочной сети для сортировки слияния Odd-Even по-прежнему независимы.