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

Мы уже реализовывали функцию merge, которая способна “слить” два отсортированных списка в один.
Чаще всего её применяют в рекурсивном алгоритме сортировки слиянием.

Напишите рекурсивную функцию merge_sort, которая производит сортировку списка.

Примечание

Ваше решение должно содержать только функции.
В решении не должно быть вызовов требуемых функций, за исключением рекурсивных.
Трассировка вызова рекурсивной функции в обработке ответа не учитывается и показана для примера.

Пример

Ввод

result = merge_sort([3, 2, 1])

Вывод

# Вызов merge_sort([3, 2, 1])
# Вызов merge_sort([3])
# Вызов merge_sort([2, 1])
# Вызов merge_sort([2])
# Вызов merge_sort([1])
result = [1, 2, 3]

Ввод

result = merge_sort([3, 1, 5, 3])

Вывод

# Вызов merge_sort([3, 1, 5, 3])
# Вызов merge_sort([3, 1])
# Вызов merge_sort([3])
# Вызов merge_sort([1])
# Вызов merge_sort([5, 3])
# Вызов merge_sort([5])
# Вызов merge_sort([3])
result = [1, 3, 3, 5]

Решение

Отличная задача для того, чтобы прочувствовать всю красоту рекурсии. Мы уже умеем сортировать слиянием отсортированные списки, теперь попробуем отсортировать один неотсортированный.

Для того, чтобы решить эту задачу нам надо прийти к двум умозаключениям:
1) любой список можно разбить на примерно равные два списка
2) если каждый из двух списков содержит один или ноль элементов, то можно их отдать на “слияние” и получить отсортированный список, который можно в свою очередь тоже отдать на слияние

Если поразмыслить над этими условиями, то “план войны” становится более-менее понятным.
В момент погружения: Делим списки на части до тех пор пока любая из частей не будет пустой или содержать только один элемент.
В момент всплытия: Сливаем полученные списки.

Поздравляю, вы только что реализовали одну из самых эффективных сортировок!

Посмотреть код

Решение

Python
def merge(left, right):
    result = []
    pos_left = pos_right = 0

    while pos_left < len(left) and pos_right < len(right):
        if left[pos_left] < right[pos_right]:
            result.append(left[pos_left])
            pos_left += 1
        else:
            result.append(right[pos_right])
            pos_right += 1

    result += left[pos_left:]
    result += right[pos_right:]

    return result


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)
Подписаться
Уведомить о
guest
9 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
nmalkam
16.01.2024 15:38

Чаще всего функцию merge применяют в рекурсивном алгоритме сортировки слиянием.

То есть алгоритм сортировка слиянием это рекурсивный алгоритм?
либо не пойму что такое рекурсивный алгоритм

Последний раз редактировалось 10 месяцев назад nmalkam ем
nmalkam
Ответить на  Сергей Клочко
17.01.2024 06:24

Спасибо большое! благодаря вашему ответу смог сам написать код. Правда он проходит только первые два теста. Я исправил код чтобы он работал если в nums будет одно число. Других ситуаций пока нем могу вспомнить.

def merge_sort(nums: list):
“””В части функции merge_sort – да, вспомогательная функция merge нерекурсивна”””

def merge(sequence_1, sequence_2):
pos1 = 0
pos2 = 0
sequence = []
while pos1 < len(sequence_1) and pos2 < len(sequence_2):
if sequence_1[pos1] > sequence_2[pos2]:
sequence.append(sequence_2[pos2])
pos2 += 1
else:
sequence.append(sequence_1[pos1])
pos1 += 1
sequence.extend(sequence_1[pos1:])
sequence.extend(sequence_2[pos2:])
return sequence
first = nums[:1]
last = nums[1:]
if len(last) > 1:
last = merge_sort(last)
res = merge(first, last)
return res
Очень рад этому, какое-то понимание начинает появляться!
Предварительно конечно написал код J. Слияние, но там уже сам не справился))))

Последний раз редактировалось 10 месяцев назад nmalkam ем
nmalkam
Ответить на  Сергей Клочко
17.01.2024 12:14

res = merge(first, last)

assert merge_sort([3, 1, 5, 3]) == [1, 3, 3, 5]
Проверку проходит

nmalkam
Ответить на  nmalkam
22.01.2024 09:42

def merge_sort(nums: list):
“””В части функции merge_sort – да, вспомогательная функция merge нерекурсивна”””

def merge(sequence_1, sequence_2):
pos1 = 0
pos2 = 0
sequence = []
while pos1 < len(sequence_1) and pos2 < len(sequence_2):
if sequence_1[pos1] > sequence_2[pos2]:
sequence.append(sequence_2[pos2])
pos2 += 1
else:
sequence.append(sequence_1[pos1])
pos1 += 1
sequence.extend(sequence_1[pos1:])
sequence.extend(sequence_2[pos2:])
return sequence
first = nums[:1]
last = nums[1:]
if len(last) > 1:
last = merge_sort(last)
return merge(first, last)

# assert merge_sort([]) == []
assert merge_sort([2, 1]) == [1, 2]
# assert merge_sort([1, 2, 3]) == [1, 2, 3]
# assert merge_sort([1, 1, 1]) == [1, 1, 1]
# assert merge_sort([-3, -2, -1]) == [-3, -2, -1]
# assert merge_sort([-3, -1, -2]) == [-3, -2, -1]
# assert merge_sort([3]) == [3]
# assert merge_sort([-2, 0, 3, 1, 0, 5, 3, -10]) == [-10, -2, 0, 0, 1, 3, 3, 5]
# assert merge_sort([3, 2, 1]) == [1, 2, 3]
assert merge_sort([3, 1, 5, 3]) == [1, 3, 3, 5]

хочу проверить останется ли форматирование кода

Ольга
Ольга
02.11.2024 13:27

Сергей, скажите, пожалуйста, рационально ли следующее решение (более компактное и, если так можно сказать, без “привязки” к индексам)?
В задаче “Сортировка слиянием” Вы писали, что решение с удалением использованных элементов из списка – очень наглядное, но неэффективное с точки зрения производительности. Прошу прощения за уточнение, но это прямо критично плохо (удаление)? Стоит ли вообще его использовать?

def merge_sort(sp):
    if len(sp) <= 1:
        return sp
    mid = len(sp) // 2
    left = merge_sort(sp[:mid])
    right = merge_sort(sp[mid:])
    return merge(left, right)

def merge(left, right):
    sp_new = []
    while left and right:
        if left[0] < right[0]:
            sp_new.append(left.pop(0))
        else:
            sp_new.append(right.pop(0))
    sp_new.extend(left)
    sp_new.extend(right)
    return sp_new

Ольга
Ольга
Ответить на  Сергей Клочко
02.11.2024 19:26

Спасибо огромное за ответ! Очень-очень все понятно.
Тогда еще вот такой вариант придумался – тоже компактный, но уже без удаления элементов.

def merge_sort(sp):
    if len(sp) <= 1:
        return sp
    mid = len(sp) // 2
    left = merge_sort(sp[:mid])
    right = merge_sort(sp[mid:])
    return merge(left, right)

def merge(left, right):
    sp_new = []
    while left and right:
        if left[0] < right[0]:
            sp_new.append(left[0])
            left = left[1:]
        else:
            sp_new.append(right[0])
            right = right[1:]
    sp_new.extend(left)
    sp_new.extend(right)
    return sp_new