Сортировка слиянием

Posted by     "Георгий Кузора" on Saturday, May 27, 2023

Что такое быстрая сортировка

Пузырьковая сортировка, сортировка вставками или сортировка выбором - это медленные виды сортировок. Скорость их выполнения O(N^2). Сортировка слиянием - это вид быстрой сортировки. Скорость ее выполнения равна O(logN*N^2).

Как работает сортировка слиянием

Принцип работы сортировки слиянием

Деление массива на части

Сортировка слиянием использует принцип деления массива на секции. Деление происходит по индексу. В качестве точки деления берется индекс середины массива. Затем для каждой из получившихся секций также происходит разделение по срединному индексу. Деление продолжается до тех пор, когда длина секций массива станет равна 1. Секция длины 1 - это отсортированный массив.

Деление массива на части

Слияние частей массива

Следующий шаг - совместить между собой отдельные секции. Секция длины 1 уже отсортирована, поэтому сортировка начинается при совмещении секций длиной 1 в новую секцию длиной 2. При совмещении элементов секции наименьший элемент из первой секции и сравнивается с наименьшим элементом во второй секции:

Слияние частей массива шаг 1

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

Слияние частей массива шаг 2

Получившийся отсортированный подмассив совмещается со следующим подмассивом его уровня.

Слияние частей массива шаг 3

После совмещения всех подмассивов, получаем отсортированный оригинальный массив.

Слияние частей массива шаг 4

Определение О-большое

Размер отсортированной секции удваивается при каждой итерации слияния. После M количества шагов размер сортированных секций будет 2^M. Как только 2^M больше N-количества элементов листе, весь лист отсортирован. Таким образом для листа размером N нужно количество шагов M равное log2(N). Каждый шаг требует прохода по всем элементам в подмассивах. Следовательно требуется N сравнений на каждом шаге. Таким образом сложность алгоритма сортировки равна O(log2(N)*N).

Реализация на языке Python

Итеративная реализация

def merge_sort(items):
 """Функция разбивает массив на секции. Собирает секции вместе"""
    n = len(items)
    temporary_storage = [None] * n
    size_of_subsections = 1 # Начальный размер секций

    while size_of_subsections < n:
    # Фукция будет выполняться пока размер секций не достигнет длины списка items.
        for i in range(0, n, size_of_subsections * 2):
        # Разбиваем список на секции и объединяем их при помощи функции merge
            i1_start, i1_end = i, min(i + size_of_subsections, n)
            i2_start, i2_end = i1_end, min(i1_end + size_of_subsections, n)
            sections = (i1_start, i1_end), (i2_start, i2_end)
            merge(items, sections, temporary_storage)
        size_of_subsections *= 2
  # Увеличиваем размер секций для следующей итерации
    return items


def merge(items, sections, temporary_storage):
 """Функция для соединения секций массива"""
    (start_1, end_1), (start_2, end_2) = sections
    i_1 = start_1
    i_2 = start_2
    i_t = 0

    while i_1 < end_1 or i_2 < end_2:
  # Выполняем соединение элементов двух секций пока не будут полностью обработаны элементы обеих секций
        if i_1 < end_1 and i_2 < end_2:
         # Условие когда элементы есть в обеих секциях
            if items[i_1] < items[i_2]:
             # Если элемент первой секции меньше, добавляем его во временное хранилище
                temporary_storage[i_t] = items[i_1]
                i_1 += 1
            else:  # the_list[i_2] >= items[i_1]
             # Если элемент второй секции меньше, добавляем его во временное хранилище
                temporary_storage[i_t] = items[i_2]
                i_2 += 1
            i_t += 1

        elif i_1 < end_1:
         # Если элементы остались только в первой секции, присоеденить их в конец временного хранилища
            for i in range(i_1, end_1):
                temporary_storage[i_t] = items[i_1]
                i_1 += 1
                i_t += 1

        else:  # i_2 < end_2
         # Если элементы остались только во второй секции, присоеденить их в конец временного хранилища
            for i in range(i_2, end_2):
                temporary_storage[i_t] = items[i_2]
                i_2 += 1
                i_t += 1

    for i in range(i_t):
     Записать все элементы из временного хранилища в оригинальный список
        items[start_1 + i] = temporary_storage[i]


arr = [11, 3, 64, 63, 23, 9, 2, 7, 10, 17]
element_index = merge_sort(arr)
print(element_index)

Рекурсивная реализация

def merge_sort(arr) -> list:
    """Рекурсивная функция для разделения и слияния секций массива"""
    if len(arr) == 1:
        # Базовый случай - длина секции равна 1
        return arr
    pivot = len(arr) // 2
    left: list = merge_sort(arr[:pivot])
    right: list = merge_sort(arr[pivot:])
    # Рекурсивно делим массив на секции, пока не достигнем базового случая
    left_index = 0
    right_index = 0
    temp_index = 0
    while left_index < len(left) and right_index < len(right):
        # Выполняем соединение элементов двух секций пока не будут полностью обработаны элементы обеих секций
        if left[left_index] < right[right_index]:
            # Если элемент первой секции меньше, добавляем его во временное хранилище
            arr[temp_index] = left[left_index]
            left_index += 1
        else:
            # Если элемент второй секции меньше, добавляем его во временное хранилище
            arr[temp_index] = right[right_index]
            right_index += 1
        temp_index += 1
    while left_index < len(left):
        # Если элементы остались только в первой секции, присоединить их в конец временного хранилища
        arr[temp_index] = left[left_index]
        left_index += 1
        temp_index += 1

    while right_index < len(right):
        # Если элементы остались только во второй секции, присоеденить их в конец временного хранилища
        arr[temp_index] = right[right_index]
        right_index += 1
        temp_index += 1
    return arr


arr = [8, 3, 7, 2, 5, 9, 1, 6, 4,]
print(merge_sort(arr))