Merge Sort
Последнее обновление
Сортировка слиянием - это алгоритм по принципу «разделяй и властвуй». Он рекурсивно делит массив пополам, пока в каждой части не останется один элемент (который тривиально отсортирован), а затем сливает части обратно в правильном порядке. Шаг слияния проходит по двум отсортированным подмассивам двумя указателями, всегда копируя следующим меньший передний элемент. Нажмите «воспроизвести» выше, чтобы увидеть, как массив пересобирается слияние за слиянием.
Поскольку деление всегда пополам, сортировка слиянием работает за время O(n log n) во всех случаях - её худший случай так же хорош, как и лучший. Расплата - O(n) дополнительной памяти под временный буфер слияния.
Временная и пространственная сложность
| Случай | Сложность | Примечания |
|---|---|---|
| Лучший случай | O(n log n) | Всегда делит вход пополам |
| Средний случай | O(n log n) | Случайный порядок |
| Худший случай | O(n log n) | Гарантировано - плохих входов нет |
| Память | O(n) | Временный буфер для слияния |
| Стабильна | Да | Ничьи при слиянии разрешаются в пользу левой части |
Шаг за шагом
| Шаг | Что происходит |
|---|---|
| 1 | Если в диапазоне 0 или 1 элемент, он уже отсортирован. |
| 2 | Разделите диапазон на две половины. |
| 3 | Рекурсивно отсортируйте левую половину сортировкой слиянием. |
| 4 | Рекурсивно отсортируйте правую половину сортировкой слиянием. |
| 5 | Слейте две отсортированные половины двумя указателями. |
Разобранный пример
Сортировка [5, 2, 4, 1]:
| Проход | Массив | Действие |
|---|---|---|
| Разделить | [5, 2] | [4, 1] | Разделите массив на две половины |
| Разделить | [5] [2] | [4] [1] | Делите снова, пока каждая часть не станет одним элементом |
| Слить | [2, 5] | [1, 4] | Слейте [5],[2] в [2, 5] и [4],[1] в [1, 4] |
| Слить | [1, 2, 4, 5] | Слейте [2, 5] и [1, 4]: выберите 1, затем 2, затем 4, затем 5 |
| Готово | [1, 2, 4, 5] | Массив полностью отсортирован |
Когда использовать сортировку слиянием
| Используйте, когда | Избегайте, когда |
|---|---|
Нужен гарантированный худший случай O(n log n) | Памяти мало, и O(n) дополнительного пространства недопустимо |
| Важна стабильность (равные ключи сохраняют порядок) | Вы сортируете небольшие массивы, где сортировка вставками быстрее |
| Вы сортируете связный список | Сортировка на месте - жёсткое требование |
| Данные слишком велики для ОЗУ (внешняя сортировка) | Доминирует локальность кэша, и проходы quicksort на месте выигрывают |
Код Merge Sort
Чистая, готовая к запуску реализация Merge Sort на Python, JavaScript, Java, C++, C. Выберите язык, скопируйте код или откройте его уже загруженным в плейграунде Coddy.
Код Merge Sort на Python
1def merge_sort(a):2 if len(a) <= 1:3 return a4 mid = len(a) // 25 left = merge_sort(a[:mid])6 right = merge_sort(a[mid:])7 return merge(left, right)8
9
10def merge(left, right):11 out = []12 i = j = 013 while i < len(left) and j < len(right):14 if left[i] <= right[j]:15 out.append(left[i])16 i += 117 else:18 out.append(right[j])19 j += 120 out.extend(left[i:])21 out.extend(right[j:])22 return out23
24
25nums = [38, 27, 43, 3, 9, 82, 10]26print("Before:", nums)27print("After: ", merge_sort(nums))Код Merge Sort на JavaScript
1function mergeSort(arr) {2 if (arr.length <= 1) return arr;3 const mid = Math.floor(arr.length / 2);4 const left = mergeSort(arr.slice(0, mid));5 const right = mergeSort(arr.slice(mid));6 return merge(left, right);7}8
9// Merge two sorted arrays into one sorted array10function merge(left, right) {11 const out = [];12 let i = 0;13 let j = 0;14 while (i < left.length && j < right.length) {15 out.push(left[i] <= right[j] ? left[i++] : right[j++]);16 }17 return out.concat(left.slice(i), right.slice(j));18}19
20const data = [5, 2, 9, 1, 7, 3];21console.log("Before:", data);22console.log("Sorted:", mergeSort(data));Код Merge Sort на Java
1import java.util.Arrays;2
3public class Main {4 static void mergeSort(int[] arr, int left, int right) {5 if (left >= right) return;6 int mid = (left + right) / 2;7 mergeSort(arr, left, mid);8 mergeSort(arr, mid + 1, right);9 merge(arr, left, mid, right);10 }11
12 // Merge two sorted halves into a temp array, then copy back13 static void merge(int[] arr, int left, int mid, int right) {14 int[] tmp = new int[right - left + 1];15 int i = left, j = mid + 1, k = 0;16 while (i <= mid && j <= right) {17 tmp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];18 }19 while (i <= mid) tmp[k++] = arr[i++];20 while (j <= right) tmp[k++] = arr[j++];21 System.arraycopy(tmp, 0, arr, left, tmp.length);22 }23
24 public static void main(String[] args) {25 int[] arr = {38, 27, 43, 3, 9, 82, 10};26 System.out.println("Before: " + Arrays.toString(arr));27 mergeSort(arr, 0, arr.length - 1);28 System.out.println("After: " + Arrays.toString(arr));29 }30}Код Merge Sort на C++
1#include <iostream>2#include <vector>3
4void printVec(const std::vector<int>& a) {5 for (int x : a) std::cout << x << " ";6 std::cout << "\n";7}8
9void merge(std::vector<int>& a, int lo, int mid, int hi) {10 std::vector<int> tmp;11 tmp.reserve(hi - lo + 1);12 int i = lo, j = mid + 1;13 while (i <= mid && j <= hi) {14 if (a[i] <= a[j]) tmp.push_back(a[i++]);15 else tmp.push_back(a[j++]);16 }17 while (i <= mid) tmp.push_back(a[i++]);18 while (j <= hi) tmp.push_back(a[j++]);19 for (size_t k = 0; k < tmp.size(); ++k) a[lo + k] = tmp[k];20}21
22void mergeSort(std::vector<int>& a, int lo, int hi) {23 if (lo >= hi) return;24 int mid = lo + (hi - lo) / 2;25 mergeSort(a, lo, mid); // sort the left half26 mergeSort(a, mid + 1, hi); // sort the right half27 merge(a, lo, mid, hi); // merge the sorted halves28}29
30int main() {31 std::vector<int> data = {38, 27, 43, 3, 9, 82, 10};32 std::cout << "Before: ";33 printVec(data);34 mergeSort(data, 0, static_cast<int>(data.size()) - 1);35 std::cout << "After: ";36 printVec(data);37 return 0;38}Код Merge Sort на C
1#include <stdio.h>2#include <stdlib.h>3
4void printArr(const int a[], int n) {5 for (int i = 0; i < n; i++) printf("%d ", a[i]);6 printf("\n");7}8
9void merge(int a[], int lo, int mid, int hi) {10 int* tmp = malloc((hi - lo + 1) * sizeof(int));11 int i = lo, j = mid + 1, k = 0;12 while (i <= mid && j <= hi) {13 if (a[i] <= a[j]) tmp[k++] = a[i++];14 else tmp[k++] = a[j++];15 }16 while (i <= mid) tmp[k++] = a[i++];17 while (j <= hi) tmp[k++] = a[j++];18 for (k = 0; k <= hi - lo; k++) a[lo + k] = tmp[k];19 free(tmp);20}21
22void mergeSort(int a[], int lo, int hi) {23 if (lo >= hi) return;24 int mid = lo + (hi - lo) / 2;25 mergeSort(a, lo, mid); // sort the left half26 mergeSort(a, mid + 1, hi); // sort the right half27 merge(a, lo, mid, hi); // merge the sorted halves28}29
30int main(void) {31 int data[] = {38, 27, 43, 3, 9, 82, 10};32 int n = sizeof(data) / sizeof(data[0]);33 printf("Before: ");34 printArr(data, n);35 mergeSort(data, 0, n - 1);36 printf("After: ");37 printArr(data, n);38 return 0;39}Часто задаваемые вопросы о сортировке слиянием
Какова временная сложность сортировки слиянием?
O(n log n) в лучшем, среднем и худшем случаях, потому что она всегда делит массив пополам. Она использует O(n) дополнительной памяти под буфер слияния.Стабильна ли сортировка слиянием?
Почему использовать сортировку слиянием вместо quicksort?
O(n log n) даже на враждебных входах и стабильна, тогда как quicksort может деградировать до O(n²). Сортировку слиянием также предпочитают для связных списков и внешней сортировки. Недостаток - O(n) дополнительной памяти.В чём разница между сортировкой слиянием и quicksort?
O(log n) памяти стека, а сортировка слиянием слепо делит пополам и сливает с помощью буфера O(n). Quicksort обычно быстрее на практике из-за локальности кэша, но сортировка слиянием имеет гарантированный худший случай O(n log n) и стабильна.Когда стоит использовать сортировку слиянием на практике?
O(n log n), при сортировке связных списков (где не нужен произвольный доступ) или при внешней сортировке данных, слишком больших для памяти. Избегайте её при нехватке памяти, так как она требует O(n) дополнительного места.Сортирует ли сортировка слиянием на месте?
O(n) для слияния двух половин, поэтому она не на месте. Существуют варианты слияния на месте, но они сложны и либо медленнее, либо теряют стабильность, поэтому версия с буфером - распространённый выбор.