Сортировка слиянием
Сортировка слиянием делит массив пополам, пока в каждой части не останется не более одного элемента. Затем объединяет отсортированные части, каждый раз выбирая меньший из первых ещё не использованных элементов. В плеере можно проследить основной массив и буфер.
До 24 значений; допустим пустой ввод. Буквы различают равные значения.
Шаг 0 / 130: Исходный массив. Делим на две части.
Массив: [8, 3a, 6a, 2, 5, 3b, 9, 1, 6b, 4]
Буфер: [·, ·, ·, ·, ·, ·, ·, ·, ·, ·]
Сравнения: 0 · Записи в буфер: 0 · Записи в массив: 0
Рамки обозначают две активные части. Жёлтый — сравнение или копирование. Фиолетовый — копии в буфере. Зелёный — итог. При копировании обратно основной ряд временно может содержать повторные копии. Позиции с нуля; правая граница не включается.
Сначала деление, затем слияние
В начальном примере десять значений. Первое деление даёт две части по пять элементов; следующие части могут иметь разную длину. Один элемент уже отсортирован. Когда обе дочерние части отсортированы, их можно объединить.
Диапазоны записаны как [start, end): левая граница включается, правая — нет. Рамки обозначают [start, middle) и [middle, end). Глубина ноль соответствует всему массиву. Плеер показывает один активный диапазон, а не полное дерево рекурсии.
Почему буфер остаётся отсортированным
Перед слиянием обе части основного ряда отсортированы. Указатели i и j обозначают первые ещё не использованные элементы, k — следующую позицию буфера. Меньший кандидат копируется в буфер, и сдвигается только указатель его исходной части.
Заполненный префикс буфера содержит наименьшие из уже выбранных элементов в отсортированном порядке. Когда одна часть заканчивается, остаток другой копируется без новых сравнений значений. После заполнения буфера весь активный диапазон копируется обратно. В буфере находятся копии: при обратном копировании основной ряд временно может содержать повторные копии одного элемента. Это промежуточное состояние, а не готовый результат сортировки.
Равные значения сохраняют порядок
Буквы a и b различают элементы, равные в исходном массиве. При равенстве кандидатов алгоритм выбирает левый: в коде используется <=. Элементы левой части изначально стояли раньше элементов правой, поэтому это правило сохраняет их относительный порядок при каждом слиянии.
Попробуйте 2, 2, 1, 2. В результате три двойки должны идти как 2a, 2b, 2c. Выбор правого кандидата при равенстве нарушил бы стабильность, хотя числовой результат оставался бы отсортированным.
Время, память и счётчики
При n ≥ 2 эта реализация работает за Θ(n log n) в лучшем, среднем и худшем случаях. На каждом уровне рекурсии копируется Θ(n) элементов, а уровней Θ(log n). Уже отсортированный вход тоже проходит через буфер: пропуска упорядоченных слияний в этой версии нет.
Переиспользуемый буфер требует O(n) дополнительной памяти, стек рекурсии — O(log n). Обёртка также копирует вход за O(n) времени и памяти, чтобы не изменять исходный массив. Итоговая дополнительная память — O(n). Для пустого и одиночного входа сравнений и записей слияния нет.
Плеер отдельно считает сравнения значений, записи в буфер и записи в основной массив. Перенос оставшегося хвоста добавляет записи, но не сравнения значений. Изменения указателей, очистка визуального буфера и начальная копия входа не учитываются. Визуализация хранит O(n log n) снимков размера O(n), поэтому верхняя оценка памяти истории — O(n² log n); это отдельные затраты, а не память самого алгоритма. Ввод ограничен 24 значениями.
Реализация на JavaScript
Эта версия сортирует конечные числа по возрастанию и возвращает новый массив. Деление, выбор при равенстве и порядок копирования через буфер совпадают с плеером; события анимации и снимки здесь опущены.
function mergeSort(values) {
const result = [...values];
const buffer = new Array(result.length);
function sort(start, end) {
if (end - start <= 1) return;
const middle = start + Math.floor((end - start) / 2);
sort(start, middle);
sort(middle, end);
let left = start;
let right = middle;
for (let k = start; k < end; k++) {
if (right === end ||
(left < middle && result[left] <= result[right])) {
buffer[k] = result[left++];
} else {
buffer[k] = result[right++];
}
}
for (let k = start; k < end; k++) result[k] = buffer[k];
}
sort(0, result.length);
return result;
}Сравнение с другими сортировками
Слияние гарантирует Θ(n log n) в худшем случае, но требует буфер. Представленные здесь вставки, пузырьковая сортировка и выбор имеют Θ(n²) в худшем случае и O(1) дополнительной памяти при сортировке на месте. Их обёртки с копированием входа дополнительно используют O(n) памяти.
Вставки и пузырьковая сортировка с ранним выходом обрабатывают отсортированный вход за Θ(n); эта сортировка слиянием всё равно требует Θ(n log n). Выбор работает за Θ(n²) при любом порядке входа и делает не более n − 1 обменов. Вставки, пузырьковая сортировка и выбранная реализация слияния стабильны; выбор с дальними обменами — нет.
Что попробовать в плеере
Сравните возрастающий и убывающий массивы одинаковой длины. Деление и число записей слияния совпадут, но число сравнений может отличаться. Затем введите повторяющиеся значения и следите за порядком букв. Одиночное значение и пустой ввод показывают базовый случай.
Визуализация использует API сцены и воспроизведения AlgiViz. Два ряда и события слияния реализованы на этом сайте.