Сортировка слиянием (merge sort) делит массив пополам, сортирует половины рекурсивно и сливает их. , , устойчива.
Зачем нужно
Квадратичные сортировки на работают около секунд. Сортировка слиянием — около 0,2 секунды. По теореме о нижней оценке быстрее сортировка сравнением не бывает — см. Сортировка.
Как работает
Слияние (Merge)
Два отсортированных массива сливаем в один, сохраняя отсортированность.
L бежит по A, R — по B, в результат пишем меньший; позиция в результате
— L + R, размер результата — lenA + lenB.
функ. Merge(arr, left, M, right, result)
// A → [left, M) B → [M, right) arr ← result
L = R = 0
A = arr + left // lenA = M − left
B = arr + M // lenB = right − M
while (L < lenA and R < lenB)
если A[L] ≤ B[R] // ≤ — для устойчивости
result[L + R] = A[L]
L++
иначе
result[L + R] = B[R]
R++
while (L < lenA)
result[L + R] = A[L]
L++
while (R < lenB)
result[L + R] = B[R]
R++
for i = 0 ... (L + R)
arr[i + left] = result[i], где . Своей памяти — : A, B и result
созданы не Merge.
Рекурсия
функ. recursiveMergeSort(arr, left, right, buffer)
// надо отсортировать arr[left; right)
если right − left = 1 // на доске; правильно — ≤ 1
return
M = (right + left) / 2 // M = left + (right − left) / 2
recursiveMergeSort(arr, left, M, buffer)
recursiveMergeSort(arr, M, right, buffer)
Merge(arr, left, M, right, buffer)
MergeSort(arr, N)
buffer = [ ... ] // размера N
recursiveMergeSort(arr, 0, N, buffer)
delete bufferДеление идёт, пока не останутся N массивов по одному элементу: , уровней. На каждом уровне слияния вместе стоят .
| Устойчива | ||
|---|---|---|
| да |
Не из лекции: доказательства
- Merge верен по инварианту: в
result[0..L+R)лежат наименьших элементов по порядку, и все они не большеA[L]иB[R].- Сортировка верна по сильной индукции по длине отрезка: при длине обе половины непустые и короче — см. Рекурсия.
- для любого N, не только степени двойки: на глубине части не длиннее , уровней . Слияние копирует всё на любом входе, поэтому это .
- Память: буфер плюс стек глубиной .
- Устойчивость: при
A[L] = B[R]берётся элемент из A, а он в исходном массиве левее.Полностью — в конспекте L04.
Тонкости
≤в Merge — это и есть устойчивость. С<сортировка остаётся правильной, но равные элементы из правой половины обгоняют левые.- Merge нельзя писать прямо в
arr: уже первый элемент из B ляжет поверх непрочитанногоA[L]. Поэтому нужен буфер, и память — , а не , как у квадратичных сортировок. - Буфер создаётся один раз в обёртке
MergeSortи переиспользуется всеми вызовами. arr + left— в C++ адрес элементаarr[left]: A и B — окна в тот же массив, а не копии.
Частые ошибки
- База
right − left = 1(как на доске): на пустом массивеrecursiveMergeSort(arr, 0, 0, …)вызывает сам себя бесконечно. Нужно≤ 1. (right + left) / 2в C++ — переполнениеintна больших индексах; пишутleft + (right − left) / 2, как в бинарном поиске.- Забыть скопировать
resultобратно вarr. deleteвместоdelete[]для буфера в C++.