- Home »

Алгоритм сортировки слиянием на Java, C и Python — как работает
Сортировка слиянием — один из самых элегантных и эффективных алгоритмов, который должен знать каждый системный администратор. Почему? Потому что практически каждая задача обработки логов, анализа данных мониторинга или работы с конфигурационными файлами сводится к сортировке огромных массивов данных. В этой статье разберём, как merge sort работает под капотом, реализуем его на Java, C и Python, и покажем, где его можно применить в реальных серверных задачах.
Главные вопросы, которые мы разберём: как работает алгоритм «разделяй и властвуй», как быстро написать эффективную реализацию на разных языках, и в каких случаях merge sort превосходит другие алгоритмы сортировки в серверных задачах.
Как работает сортировка слиянием
Merge sort — это классический алгоритм типа “divide and conquer” (разделяй и властвуй). Принцип простой: делим массив пополам до тех пор, пока не получим массивы из одного элемента, затем сливаем их обратно в отсортированном порядке.
Алгоритм состоит из двух основных фаз:
- Разделение — рекурсивно делим массив пополам
- Слияние — объединяем отсортированные подмассивы в один отсортированный массив
Временная сложность: O(n log n) во всех случаях. Пространственная сложность: O(n). Это делает merge sort предсказуемым и стабильным выбором для критических серверных задач.
Реализация на Java
Java-реализация особенно полезна для серверных приложений, где нужна производительность и типобезопасность:
public class MergeSort {
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
// Рекурсивно сортируем левую и правую части
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// Сливаем отсортированные части
merge(arr, left, mid, right);
}
}
private static void merge(int[] arr, int left, int mid, int right) {
// Размеры временных массивов
int n1 = mid - left + 1;
int n2 = right - mid;
// Создаём временные массивы
int[] leftArr = new int[n1];
int[] rightArr = new int[n2];
// Копируем данные во временные массивы
System.arraycopy(arr, left, leftArr, 0, n1);
System.arraycopy(arr, mid + 1, rightArr, 0, n2);
// Сливаем временные массивы обратно в arr[left..right]
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (leftArr[i] <= rightArr[j]) {
arr[k] = leftArr[i];
i++;
} else {
arr[k] = rightArr[j];
j++;
}
k++;
}
// Копируем оставшиеся элементы
while (i < n1) {
arr[k] = leftArr[i];
i++;
k++;
}
while (j < n2) {
arr[k] = rightArr[j];
j++;
k++;
}
}
public static void main(String[] args) {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
System.out.println("Исходный массив: " + Arrays.toString(arr));
mergeSort(arr, 0, arr.length - 1);
System.out.println("Отсортированный массив: " + Arrays.toString(arr));
}
}
Реализация на C
C-версия идеальна для системного программирования и работы с ограниченными ресурсами:
#include
#include
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
// Создаём временные массивы
int *leftArr = (int*)malloc(n1 * sizeof(int));
int *rightArr = (int*)malloc(n2 * sizeof(int));
// Копируем данные во временные массивы
for (int i = 0; i < n1; i++)
leftArr[i] = arr[left + i];
for (int j = 0; j < n2; j++)
rightArr[j] = arr[mid + 1 + j];
// Сливаем временные массивы обратно в arr[left..right]
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (leftArr[i] <= rightArr[j]) {
arr[k] = leftArr[i];
i++;
} else {
arr[k] = rightArr[j];
j++;
}
k++;
}
// Копируем оставшиеся элементы
while (i < n1) {
arr[k] = leftArr[i];
i++;
k++;
}
while (j < n2) {
arr[k] = rightArr[j];
j++;
k++;
}
free(leftArr);
free(rightArr);
}
void mergeSort(int arr[], int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
// Рекурсивно сортируем левую и правую части
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// Сливаем отсортированные части
merge(arr, left, mid, right);
}
}
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int arr_size = sizeof(arr) / sizeof(arr[0]);
printf("Исходный массив: ");
printArray(arr, arr_size);
mergeSort(arr, 0, arr_size - 1);
printf("Отсортированный массив: ");
printArray(arr, arr_size);
return 0;
}
Реализация на Python
Python-версия самая лаконичная и идеальна для скриптов автоматизации:
def merge_sort(arr):
if len(arr) <= 1:
return arr
# Делим массив пополам
mid = len(arr) // 2
left = arr[:mid]
right = arr[mid:]
# Рекурсивно сортируем левую и правую части
left = merge_sort(left)
right = merge_sort(right)
# Сливаем отсортированные части
return merge(left, right)
def merge(left, right):
result = []
i, j = 0, 0
# Сливаем элементы в отсортированном порядке
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
# Добавляем оставшиеся элементы
result.extend(left[i:])
result.extend(right[j:])
return result
# Пример использования
if __name__ == "__main__":
arr = [64, 34, 25, 12, 22, 11, 90]
print("Исходный массив:", arr)
sorted_arr = merge_sort(arr)
print("Отсортированный массив:", sorted_arr)
# Альтернативная in-place версия для экономии памяти
def merge_sort_inplace(arr, left=0, right=None):
if right is None:
right = len(arr) - 1
if left < right:
mid = left + (right - left) // 2
merge_sort_inplace(arr, left, mid)
merge_sort_inplace(arr, mid + 1, right)
merge_inplace(arr, left, mid, right)
def merge_inplace(arr, left, mid, right):
left_arr = arr[left:mid + 1]
right_arr = arr[mid + 1:right + 1]
i = j = 0
k = left
while i < len(left_arr) and j < len(right_arr):
if left_arr[i] <= right_arr[j]:
arr[k] = left_arr[i]
i += 1
else:
arr[k] = right_arr[j]
j += 1
k += 1
while i < len(left_arr):
arr[k] = left_arr[i]
i += 1
k += 1
while j < len(right_arr):
arr[k] = right_arr[j]
j += 1
k += 1
Сравнение производительности
Вот сравнительная таблица алгоритмов сортировки для серверных задач:
Алгоритм | Лучший случай | Худший случай | Средний случай | Память | Стабильность |
---|---|---|---|---|---|
Merge Sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Да |
Quick Sort | O(n log n) | O(n²) | O(n log n) | O(log n) | Нет |
Heap Sort | O(n log n) | O(n log n) | O(n log n) | O(1) | Нет |
Bubble Sort | O(n) | O(n²) | O(n²) | O(1) | Да |
Практические кейсы применения
Положительные кейсы:
- Обработка логов — merge sort идеален для сортировки больших файлов логов по времени
- Анализ метрик — стабильная сортировка сохраняет относительный порядок равных элементов
- Базы данных — многие СУБД используют модификации merge sort для внешней сортировки
- Параллельные вычисления — легко распараллеливается на многоядерных серверах
Отрицательные кейсы:
- Ограниченная память — требует дополнительной памяти O(n)
- Маленькие массивы — overhead рекурсии может быть избыточным
- Уже отсортированные данные — не использует преимущества частичной сортировки
Оптимизации для серверов
Для серверных задач можно применить несколько оптимизаций:
# Python-скрипт для сортировки логов с оптимизациями
import os
import mmap
from datetime import datetime
def optimized_merge_sort(arr, threshold=10):
"""
Гибридная версия merge sort с переключением на insertion sort
для маленьких массивов
"""
if len(arr) <= threshold:
return insertion_sort(arr)
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = optimized_merge_sort(arr[:mid], threshold)
right = optimized_merge_sort(arr[mid:], threshold)
return merge(left, right)
def insertion_sort(arr):
"""Insertion sort для маленьких массивов"""
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr
def sort_large_log_file(filename):
"""Сортировка больших файлов логов с использованием mmap"""
with open(filename, 'r+b') as f:
with mmap.mmap(f.fileno(), 0) as mm:
lines = mm.read().decode('utf-8').splitlines()
# Парсим timestamp и сортируем
log_entries = []
for line in lines:
try:
timestamp_str = line.split()[0] + ' ' + line.split()[1]
timestamp = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
log_entries.append((timestamp, line))
except:
continue
# Сортируем по timestamp
sorted_entries = optimized_merge_sort(log_entries, key=lambda x: x[0])
# Записываем обратно
sorted_lines = [entry[1] for entry in sorted_entries]
return sorted_lines
Интеграция с системными утилитами
Merge sort можно эффективно комбинировать с системными утилитами:
#!/bin/bash
# Скрипт для сортировки логов с использованием внешних утилит
# Предварительная сортировка больших файлов
sort_large_files() {
local input_dir="$1"
local output_dir="$2"
# Создаём временные отсортированные файлы
for file in "$input_dir"/*.log; do
basename=$(basename "$file")
# Используем GNU sort для предварительной сортировки
sort -t' ' -k1,2 "$file" > "$output_dir/sorted_$basename"
done
# Объединяем отсортированные файлы
sort -m "$output_dir"/sorted_*.log > "$output_dir/final_sorted.log"
}
# Пример использования с Python
python3 -c "
import subprocess
import sys
def merge_with_external_sort(files):
'''Используем внешнюю утилиту sort для слияния'''
cmd = ['sort', '-m'] + files
result = subprocess.run(cmd, capture_output=True, text=True)
return result.stdout.splitlines()
# Обрабатываем аргументы
files = sys.argv[1:]
if files:
sorted_lines = merge_with_external_sort(files)
for line in sorted_lines:
print(line)
" /var/log/app1.log /var/log/app2.log
Автоматизация и мониторинг
Для автоматизации серверных задач можно создать универсальный скрипт сортировки:
#!/usr/bin/env python3
"""
Универсальный скрипт сортировки для серверных задач
"""
import argparse
import os
import time
import logging
from pathlib import Path
class ServerSorter:
def __init__(self, algorithm='merge', threshold=1000):
self.algorithm = algorithm
self.threshold = threshold
self.stats = {'comparisons': 0, 'memory_usage': 0}
def sort_file(self, filepath, output_path=None, key_func=None):
"""Сортировка файла с метриками производительности"""
start_time = time.time()
with open(filepath, 'r') as f:
lines = f.readlines()
if key_func:
# Сортировка с кастомным ключом
sorted_lines = self.merge_sort_with_key(lines, key_func)
else:
sorted_lines = self.merge_sort(lines)
if output_path:
with open(output_path, 'w') as f:
f.writelines(sorted_lines)
end_time = time.time()
logging.info(f"Sorted {len(lines)} lines in {end_time - start_time:.2f}s")
return sorted_lines
def merge_sort_with_key(self, arr, key_func):
"""Merge sort с кастомной функцией ключа"""
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = self.merge_sort_with_key(arr[:mid], key_func)
right = self.merge_sort_with_key(arr[mid:], key_func)
return self.merge_with_key(left, right, key_func)
def merge_with_key(self, left, right, key_func):
"""Слияние с кастомным ключом"""
result = []
i, j = 0, 0
while i < len(left) and j < len(right):
self.stats['comparisons'] += 1
if key_func(left[i]) <= key_func(right[j]):
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
def main():
parser = argparse.ArgumentParser(description='Server file sorter')
parser.add_argument('input_file', help='Input file to sort')
parser.add_argument('-o', '--output', help='Output file')
parser.add_argument('-k', '--key', help='Sort key (timestamp, size, etc.)')
parser.add_argument('-t', '--threshold', type=int, default=1000,
help='Threshold for hybrid sorting')
args = parser.parse_args()
sorter = ServerSorter(threshold=args.threshold)
# Определяем функцию ключа
key_func = None
if args.key == 'timestamp':
key_func = lambda line: line.split()[0] if line.split() else ''
elif args.key == 'size':
key_func = lambda line: int(line.split()[4]) if len(line.split()) > 4 else 0
sorter.sort_file(args.input_file, args.output, key_func)
if __name__ == '__main__':
main()
Развёртывание на сервере
Для тестирования и развёртывания алгоритмов сортировки понадобится производительный сервер. Можете заказать VPS или выделенный сервер для полноценного тестирования на больших объёмах данных.
Интересные факты и нестандартные применения
- Внешняя сортировка — merge sort лежит в основе сортировки файлов, не помещающихся в память
- Параллельная версия — можно распараллелить на любое количество потоков
- Инверсии массива — модификация merge sort может подсчитать количество инверсий за O(n log n)
- Стабильность — единственный из быстрых алгоритмов, который сохраняет относительный порядок равных элементов
Пример подсчёта инверсий (полезно для анализа данных):
def merge_sort_count_inversions(arr):
"""Подсчёт инверсий во время сортировки"""
if len(arr) <= 1:
return arr, 0
mid = len(arr) // 2
left, left_inv = merge_sort_count_inversions(arr[:mid])
right, right_inv = merge_sort_count_inversions(arr[mid:])
merged, split_inv = merge_count_inversions(left, right)
return merged, left_inv + right_inv + split_inv
def merge_count_inversions(left, right):
"""Слияние с подсчётом инверсий"""
result = []
i, j = 0, 0
inv_count = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
inv_count += len(left) - i # Все элементы left[i:] больше right[j]
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result, inv_count
Похожие решения и альтернативы
Если merge sort не подходит для ваших задач, рассмотрите альтернативы:
- Timsort — гибридный алгоритм, используемый в Python и Java (Collections.sort)
- Introsort — гибрид quicksort, heapsort и insertion sort (C++ std::sort)
- Radix Sort — для целых чисел и строк фиксированной длины
- External Sort — для сортировки файлов, не помещающихся в память
Полезные ссылки:
Выводы и рекомендации
Merge sort — это надёжный выбор для серверных задач, где критически важна предсказуемость производительности. Используйте его когда:
- Нужна стабильная сортировка (сохранение относительного порядка равных элементов)
- Данные поступают из внешних источников и могут быть в худшем случае
- Есть достаточно оперативной памяти
- Планируется параллельное выполнение
Не используйте merge sort если:
- Память сильно ограничена (лучше heap sort)
- Данные часто уже частично отсортированы (лучше timsort)
- Нужна максимальная производительность на случайных данных (лучше tuned quicksort)
Для серверных задач merge sort особенно хорош в скриптах автоматизации, обработке логов и анализе данных мониторинга. Комбинируйте его с системными утилитами и не забывайте про оптимизации для конкретных случаев использования.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.