Radix Sort — самая быстрая сортировка для чисел и строк
medium

Radix Sort — самая быстрая сортировка для чисел и строк

Объясняем, в чём секрет поразрядной сортировки

Сортировки нужны для ускорения работы программ: когда у нас много данных, нам нужно как-то их организовать, чтобы по ним было легко искать и быстро обрабатывать. На небольших наборах данных это незаметно, но когда нужно работать с сотнями тысяч единиц данных, сортировка может либо ускорить работу программы, либо намертво её повесить. 

Люди изобрели много сортировок для разных данных и под разные задачи. Сегодня расскажем про поразрядную сортировку — это почти как сортировка по алфавиту, но для данных. В английском языке это называется Radix sort — сортировка по основанию системы счисления.

Чтобы оценить скорость работы этого алгоритма, посмотрите видео — там собраны 24 популярных алгоритма, которые обрабатывают один массив данных. Поразрядная сортировка — вторая слева в нижнем ряду. Вы узнаете её по тому, что она закончит работу в числе первых: 

Сегодняшняя статья будет полезна для расширения компьютерного кругозора и погружения в мир информатики. Она не особо нужна для прикладной разработки, потому что там все эти сортировки уже реализованы и работают автоматически. Но чем круче вы в мире ИТ, тем больше вы будете ценить гениальность поразрядной сортировки.

Главное коротко: алгоритм поразрядной сортировки гениален в том, что сортирует не числа целиком, а значения разрядов. Получается, что он как бы разбирается с числами на уровне единиц, десятков, сотен и т. д. и только потом он делает общую сортировку. Это позволяет ему не бегать по всем сравниваемым числам и не делать миллион сравнений. Отсюда и экономия времени.

Что такое основание и разряд

Чтобы проще было вникнуть в материал, вспомним про основание чисел. Вот короткая версия:

  • мы пользуемся десятичной системой счисления — это значит, что в основании этой системы стоит число 10;
  • это значит, что, когда мы доходим до 9 и продолжаем увеличивать, девятка превращается в ноль, а мы добавляем единицу в следующий разряд;
  • каждое число можно представить как сумму произведений из разрядов и числа 10 в нужной степени;
  • например, 547 = 5×10² + 4×10¹ + 7×10⁰, где 10 — это основание системы счисления, а степени — порядковый номер числа справа налево, начиная с нуля. Этот порядковый номер и называется разрядом.

Принцип работы поразрядной сортировки

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

Например, чтобы понять, как отсортировать числа [137137105157, 24395739293, 474290561035, 5, 276, 42], алгоритм сделает так:

  1. Подготовка. Алгоритм узнает, сколько элементов в массиве — 6.
  2. Узнает, сколько разрядов у самого длинного числа — 12 (возьмёт «длину» числа 137137105157).
  3. Создаст промежуточный массив с 10 пустыми ячейками (10 — выбранное основание системы счисления).
  4. Первый разряд. Найдёт все значения первого разряда в каждом числе → 2, 3, 5, 6 и 7.
  5. Положит соответствующие числа в ячейки под этими номерами
    [[], [], [42], [24395739293], [], [474290561035, 5], [276], [137137105157], [], []].
  6. Заменит содержимое исходного массива этими непустыми значениями: [42, 24395739293, 474290561035, 5, 276, 13713710515.
  7. Второй разряд. Теперь то же самое сделает со вторым разрядом: найдёт все значения второго разряда в каждом числе → 0, 3,4 ,5, 7,9. Ноль — потому что второй разряд у пятёрки — нулевой, 5 = 05.
  8. Положит соответствующие числа в ячейки под этими номерами
    [[5], [], [], [474290561035], [42], [137137105157], [], [276], [], [24395739293]].
  9. Заменит содержимое исходного массива этими непустыми значениями: [5, 474290561035, 42, 137137105157, 276, 24395739293].
  10. Третий разряд. То же самое сделает с третьим разрядом: посмотрит на значение числа, стоящего на третьем месте справа налево: 0, 1, 2.
  11. Положит соответствующие числа в ячейки под этими номерами: [[5, 474290561035, 42], [137137105157], [276, 24395739293], [], [], [], [], [], [], []].
  12.  Заменит содержимое массива этими непустыми значениями: [5, 474290561035, 42, 137137105157, 276, 24395739293].
  13. Четвёртый разряд. То же самое сделает с четвёртым разрядом: посмотрит на значение числа, стоящего на третьем месте справа налево: 0 (количество тысяч у первых трёх маленьких чисел), 1, 5 и 9.
  14. Положит соответствующие числа в ячейки под этими номерами: [[5, 42, 276], [474290561035], [], [], [], [137137105157], [], [], [], [24395739293]].
  15. Заменит содержимое массива этими непустыми значениями: [5, 42, 276, 474290561035, 137137105157, 24395739293].
  16. И так далее. Сделает так ещё восемь раз.
  17. В итоге получит отсортированный массив без сравнений: [5, 42, 276, 24395739293, 137137105157, 474290561035].

Пишем алгоритм

Чтобы написать алгоритм поразрядной сортировки, используем Python — с ним код будет понятнее. Ещё мы будем в одном месте использовать нижнее подчёркивание в качестве имени переменной — с точки зрения синтаксиса так можно, это такое же имя переменной, как x или max_count. Смысл в том, что разработчики договорились использовать нижнее подчёркивание как переменную там, где переменная нужна только один раз, например при организации цикла через range(). Проще говоря, нижнее подчёркивание говорит нам, что эта переменная больше нигде не используется, только в этом месте.

Ещё для удобства мы добавили в алгоритм вывод промежуточных значений на каждом шаге — так проще понять, как двигаются числа внутри массивов в зависимости от разрядов.

# поразрядная сортировка
def radix_sort(arr):
    
    # находим размер самого длинного числа
    max_digits = max([len(str(x)) for x in arr])

    # основание системы счисления
    base = 10

    # создаём промежуточный пустой массив из 10 элементов
    bins = [[] for _ in range(base)]

    # перебираем все разряды, начиная с нулевого
    for i in range(0, max_digits):
        # для удобства выводим текущий номер разряда, с которым будем работать
        print('✅ Номер разряда → ' + str(i))
        # перебираем все элементы в массиве
        for x in arr:
            # получаем цифру, стоящую на текущем разряде в каждом числе массива
            digit = (x // base ** i) % base
            # отправляем число в промежуточный массив в ячейку, которая совпадает со значением этой цифры 
            bins[digit].append(x)
        # собираем в исходный массив все ненулевые значения из промежуточного массива
        arr = [x for queue in bins for x in queue]
        # текущее состояние массива
        print(arr)
        # текущее состояние промежуточного массива
        print(bins)

        # очищаем промежуточный массив
        bins = [[] for _ in range(base)]

    # возвращаем отсортированный массив
    return arr


# запускаем сортировку
print(radix_sort([137137105157, 24395739293, 474290561035, 5, 276, 42]))
Видно, как числа разбираются по ячейкам в зависимости от текущего разряда

Корректор:

Ира Михеева

Художник:

Алексей Сухов

Вёрстка:

Кирилл Климентьев

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Вам может быть интересно
medium
[anycomment]
Exit mobile version