Home » Алгоритм сортировки слиянием на Java, C и Python — как работает
Алгоритм сортировки слиянием на Java, C и Python — как работает

Алгоритм сортировки слиянием на 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 особенно хорош в скриптах автоматизации, обработке логов и анализе данных мониторинга. Комбинируйте его с системными утилитами и не забывайте про оптимизации для конкретных случаев использования.


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked