Коротка відповідь
middleMerge Sort — це розділяй і володарюй алгоритм, що рекурсивно ділить масив на половини й зливає їх у відсортований порядок. Він гарантує O(n log n) час і O(n) пам’ять незалежно від вхідних даних. Підходить, коли потрібна стабільність і контроль над складністю.
Повне пояснення
Коротке пояснення
Merge Sort розбиває масив на дві частини, сортує кожну рекурсивно і потім об’єднує їх у один відсортований масив. Ідея полягає в тому, що об’єднання двох вже відсортованих списків можна виконати за лінійний час.
Аналогія
Уявіть, що ви маєте два купи книг, кожна вже упорядкована за розміром. Щоб отримати одну купу, ви берете найменшу книгу з кожної купи і кладете її в новий список, продовжуючи до тих пір, поки обидві купи не порожні.
Коли застосовувати
- Масив великий, і потрібна гарантована O(n log n) складність.
- Потрібно стабільне сортування (зберігає порядок однакових елементів).
- Пам’ять достатня для додаткового простору O(n).
- Дані не піддаються швидкому вставленню/видаленню, як у бінарному дереві.
Кроки алгоритму
- Якщо масив має 0 або 1 елемент, повернути його.
- Знайти середину масиву і розділити на ліву та праву частини.
- Рекурсивно викликати Merge Sort для кожної половини.
- Об’єднати дві відсортовані частини у один масив, порівнюючи їх елементи.
- Повернути об’єднаний масив.
Візуальна інтуїція (текстова)
[4, 1, 3, 2]
├─ розділити → [4, 1] | [3, 2]
│ ├─ розділити → [4] | [1]
│ │ → повернути [1, 4]
│ ├─ розділити → [3] | [2]
│ │ → повернути [2, 3]
│ └─ об’єднати → [1, 4] + [2, 3] = [1, 2, 3, 4]
└─ повернути
Складність
Часова складність: O(n log n) у найгіршому, середньому й кращому випадках. Пам’ятна складність: O(n) через тимчасовий масив під час злиття.
Код (js)
// Merge Sort: рекурсивне розділення і злиття
function mergeSort(array) {
// Якщо масив має 0 або 1 елемент, він вже відсортований
if (array.length <= 1) {
return array;
}
// Знаходимо середину масиву для розділення
const middle = Math.floor(array.length / 2);
const leftHalf = array.slice(0, middle); // лівий підмасив
const rightHalf = array.slice(middle); // правий підмасив
// Рекурсивно сортуємо обидві половини
const sortedLeft = mergeSort(leftHalf);
const sortedRight = mergeSort(rightHalf);
// Об’єднуємо відсортовані половини
return merge(sortedLeft, sortedRight);
}
// Функція злиття двох відсортованих масивів
function merge(left, right) {
const result = [];
let i = 0; // індекс для лівого масиву
let j = 0; // індекс для правого масиву
// Порівнюємо елементи обох масивів і додаємо найменший
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
result.push(left[i]);
i++;
} else {
result.push(right[j]);
j++;
}
}
// Якщо залишилися елементи в лівому масиві, додаємо їх
while (i < left.length) {
result.push(left[i]);
i++;
}
// Якщо залишилися елементи в правому масиві, додаємо їх
while (j < right.length) {
result.push(right[j]);
j++;
}
return result; // повертаємо об’єднаний масив
}
// Тестовий приклад
console.log(mergeSort([4, 1, 3, 2])); // очікуваний результат: [1, 2, 3, 4]
Типові помилки і поради
- Використання slice без копіювання –
sliceстворює новий масив, тому пам’ять збільшується. Якщо потрібна ін‑плейс оптимізація, використовуйте індекси. - Неправильне обчислення середини –
Math.floor(array.length / 2)гарантує, що масив розділяється рівномірно. - Забуття об’єднання – без функції
mergeрекурсія не поверне відсортований результат. - Перевантаження стеку – для дуже великих масивів можна замінити рекурсію на ітеративний підхід.
- Нестабільність – Merge Sort стабільний, тому однакові елементи зберігають порядок.
Перевір себе
- Яка складність Merge Sort у найгіршому випадку?
- Чому алгоритм стабільний, а Quick Sort ні за замовчуванням?