Menu
Coddy logo textTech

Counting Sort

Последнее обновление

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

Сортировка подсчётом выполняется за время O(n + k), где k - это диапазон входных значений. Когда k ненамного больше n, она чрезвычайно быстра и может превзойти сортировки сравнением с O(n log n), но если диапазон значений огромен, массив подсчётов размером O(k) делает её непрактичной.

Временная и пространственная сложность

СлучайСложностьПримечания
ВремяO(n + k)n элементов, k = диапазон значений
ПамятьO(n + k)Массив подсчётов + выходной массив
УстойчиваяДаПри размещении справа налево с использованием префиксных сумм
Сравнения?НетСортирует подсчётом, а не сравнением
Лучше всего дляМалого диапазона целыхk близко к n

Пошагово

ШагЧто происходит
1Найти максимальное значение, чтобы задать размер массива подсчётов.
2Подсчитать, сколько раз встречается каждое значение.
3(Опционально) Преобразовать подсчёты в префиксные суммы для устойчивости.
4Записать каждое значение в выход столько раз, сколько оно встречается.
5Выходной массив теперь полностью отсортирован.

Разобранный пример

Сортировка [1, 4, 1, 2, 4] (значения от 0 до 4, поэтому массив подсчётов имеет 5 ячеек):

ПроходСостояниеДействие
Проход по входуcount = [0, 2, 1, 0, 2]Подсчитать вхождения: 1 встречается дважды, 2 один раз, 4 дважды.
Префиксные суммыcount = [0, 2, 3, 3, 5]Каждая ячейка теперь хранит, сколько значений <= её индекса, что задаёт итоговые позиции.
Разместить 4output = [_, _, _, _, 4]Читаем справа налево: count[4] = 5, поэтому 4 идёт в индекс 4; уменьшаем до 4.
Разместить 2output = [_, _, 2, _, 4]count[2] = 3, поэтому 2 идёт в индекс 2; уменьшаем до 2.
Разместить 1output = [_, 1, 2, _, 4]count[1] = 2, поэтому 1 идёт в индекс 1; уменьшаем до 1.
Разместить 4output = [_, 1, 2, 4, 4]count[4] = 4, поэтому эта 4 идёт в индекс 3; уменьшаем до 3.
Разместить 1output = [1, 1, 2, 4, 4]count[1] = 1, поэтому эта 1 идёт в индекс 0. Массив отсортирован.

Когда использовать сортировку подсчётом

Использовать, когдаИзбегать, когда
Вы сортируете целые числа (или ключи, отображаемые в целые) в малом известном диапазоне.Диапазон значений k намного больше числа элементов n.
Вам нужно линейное время O(n + k) и вы можете позволить себе дополнительные массивы.Памяти мало - массив подсчётов стоит O(k) независимо от n.
Вам нужна устойчивая сортировка как подпрограмма (например, внутри поразрядной сортировки).Ключи - это числа с плавающей точкой, строки или произвольные объекты без отображения в целые.
Максимальное значение ограничено и его дёшево вычислить заранее.Диапазон неизвестен или не ограничен, поэтому вы не можете задать размер массива подсчётов.

Код Counting Sort

Чистая, готовая к запуску реализация Counting Sort на Python, JavaScript, Java, C++, C. Выберите язык, скопируйте код или откройте его уже загруженным в плейграунде Coddy.

Код Counting Sort на Python

Python
1def counting_sort(a):2    # Works for non-negative integers with a small max value3    counts = [0] * (max(a) + 1)4    for value in a:5        counts[value] += 16    # Prefix sums turn counts into final positions7    for i in range(1, len(counts)):8        counts[i] += counts[i - 1]9    out = [0] * len(a)10    for value in reversed(a):  # reversed keeps equal values stable11        counts[value] -= 112        out[counts[value]] = value13    return out14
15
16nums = [4, 2, 2, 8, 3, 3, 1]17print("Before:", nums)18print("After: ", counting_sort(nums))
Запустите этот код в плейграунде Python

Частые вопросы о сортировке подсчётом

Какова временная сложность сортировки подсчётом?
Сортировка подсчётом имеет сложность O(n + k), где n - число элементов, а k - диапазон возможных значений. Когда k = O(n), это линейное время. Она использует O(n + k) дополнительной памяти.
Устойчива ли сортировка подсчётом?
Может быть. Устойчивая версия строит префиксные суммы подсчётов и размещает элементы справа налево, что сохраняет относительный порядок равных ключей. Простая версия «перезаписи по значению», показанная здесь, даёт корректную сортировку, но используется в основном для обычных целых чисел.
Когда стоит использовать сортировку подсчётом?
Используйте её при сортировке целых чисел (или ключей, отображаемых в целые) в малом известном диапазоне. Если диапазон значений k намного больше числа элементов, массив подсчётов расходует память впустую, и лучше подойдёт сортировка сравнением.
В чём разница между сортировкой подсчётом и поразрядной сортировкой (radix sort)?
Сортировка подсчётом сортирует по всему значению за один проход и требует массив подсчётов размером с диапазон значений. Поразрядная сортировка сортирует разряд за разрядом и обычно вызывает устойчивую сортировку подсчётом для каждого разряда, что удерживает диапазон на проход малым (например, 10 для десятичных цифр). Поразрядная сортировка справляется с большими диапазонами значений, которые сделали бы одну сортировку подсчётом непрактичной.
Почему сортировка подсчётом не всегда быстрее быстрой сортировки (quicksort)?
Сортировка подсчётом имеет сложность O(n + k), поэтому выигрывает только когда диапазон значений k сопоставим с n. Если k огромен - например, сортировка 100 значений в диапазоне от 0 до 1,000,000,000 - массив подсчётов O(k) доминирует и расходует память, тогда как сортировка сравнением с O(n log n), такая как quicksort, остаётся быстрой и экономной по памяти.
Может ли сортировка подсчётом работать с отрицательными числами?
Да, с небольшим смещением. Вместо индексирования массива подсчётов напрямую по значению индексируйте его по value - min, чтобы наименьшее значение отображалось в индекс 0. Размер массива подсчётов становится max - min + 1. Забыть про это смещение - распространённая ошибка, приводящая к сбою на отрицательных входных данных.
Coddy programming languages illustration

Освойте алгоритмы с Coddy

НАЧАТЬ