Алгоритм сортировки естественным слиянием (Natural Merge Sort): принцип работы
Также упоминается как: Естественное двухпутевое слияние
Время работы в лучшем случае (почти отсортированный массив) составляет O(N), а в худшем - O(N log N).
Читать дальше
Тим Питерс, один из ключевых разработчиков Python (и автор знаменитого "Дзена Питона"), заметил важную вещь: в реальном мире данные почти никогда не бывают абсолютно случайными. В массивах часто уже есть отсортированные куски (например, логи отсортированы по времени, но с небольшими сбоями). Классическая сортировка слиянием игнорирует этот факт, выполняя лишнюю работу.
Timsort работает как гениальный пылесос: он идет по массиву и собирает "естественные серии" (runs). Если серия убывает, он мгновенно разворачивает ее в памяти. Если серия слишком короткая (меньше так называемого minrun), он достраивает ее до нужной длины с помощью быстрой и компактной сортировки вставкой. Затем он начинает хитро сливать эти серии, поддерживая баланс длин серий в специальном стеке (чтобы слияние шло эффективно).
Интересный факт: Алгоритм Timsort считался безупречным более 10 лет. Но в 2015 году европейские исследователи, использующие формальную верификацию кода (доказательство теорем), обнаружили глубоко запрятанный баг в логике балансировки стека Timsort. Баг мог привести к падению программы при сортировке специально подобранных массивов огромного размера. Ошибку быстро исправили во всех языках программирования, но этот случай вошел в историю как пример важности математического доказательства корректности алгоритмов.
Идея метода сортировки
Естественное слияние работает "снизу вверх" (bottom-up), не используя рекурсию. Мы проходим по массиву и объединяем смежные отсортированные серии.
- Шаг 1. Сканируем массив слева направо и определяем границы первой отсортированной серии (от элемента, где массив перестает возрастать).
- Шаг 2. Сканируем дальше, определяя вторую серию.
- Шаг 3. Вызываем процедуру
Merge()(ту самую, из предыдущего урока) для слияния этих двух серий в одну длинную. - Шаг 4. Продолжаем процесс до конца массива. Если мы дошли до конца и сделали более одной склейки, начинаем проход по массиву заново.
- Конец: Если за весь проход от начала до конца мы нашли только одну серию - массив отсортирован!
Интерактивная модель демонстрирует суть естественного слияния: алгоритм ищет в массиве уже готовые упорядоченные серии и объединяет их.
Тренажер: алгоритм сортировки естественным слиянием
Отсортируем массив 4 7 2 5 1 6 3, находя и сливая готовые серии.
Анализ сложности
Прелесть этого алгоритма в его адаптивности. Если подать на вход уже отсортированный массив, алгоритм за один проход определит, что серия всего одна, и завершит работу за время O(N). Классический Merge Sort все равно бы потратил O(N log N).
В худшем случае (массив отсортирован по убыванию) алгоритм будет находить серии длиной в 1 элемент и, по сути, сведется к классической восходящей сортировке слиянием. Время работы будет O(N log N).
Реализация на C
Естественное слияние: код целиком
// Функция возвращает индекс конца текущей серии
int GetNextRun(int arr[], int n, int start) {
int i = start;
while (i < n - 1 && arr[i] <= arr[i + 1]) {
i++;
}
return i;
}
// Процедура слияния (Merge) аналогична классической
// void Merge(int arr[], int left, int mid, int right) { ... }
void NaturalMergeSort(int arr[], int n) {
int num_runs = 0;
// Пока не останется всего одна серия на весь массив
do {
num_runs = 0;
int i = 0;
while (i < n) {
// Находим первую серию
int end1 = GetNextRun(arr, n, i);
num_runs++;
// Если первая серия дошла до конца массива - выходим
if (end1 == n - 1) break;
// Находим вторую серию
int end2 = GetNextRun(arr, n, end1 + 1);
// Сливаем их
Merge(arr, i, end1, end2);
num_runs++; // Считаем получившуюся общую серию
i = end2 + 1; // Сдвигаем указатель для следующих пар
}
} while (num_runs > 1);
}
Коротко: главное
- Естественное слияние - адаптивная оптимизация классической сортировки слиянием.
- Вместо слепого деления алгоритм сканирует массив и находит уже отсортированные последовательности (серии).
- Слияние найденных серий позволяет отсортировать почти упорядоченный массив за время O(N).
- В худшем случае (обратный порядок) время работы составляет O(N log N).
- Именно этот принцип лежит в основе гибридного алгоритма Timsort (стандарт в Python).
Проверьте себя
Чему равно время работы Естественного слияния на уже отсортированном массиве?