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] | Каждая ячейка теперь хранит, сколько значений <= её индекса, что задаёт итоговые позиции. |
Разместить 4 | output = [_, _, _, _, 4] | Читаем справа налево: count[4] = 5, поэтому 4 идёт в индекс 4; уменьшаем до 4. |
Разместить 2 | output = [_, _, 2, _, 4] | count[2] = 3, поэтому 2 идёт в индекс 2; уменьшаем до 2. |
Разместить 1 | output = [_, 1, 2, _, 4] | count[1] = 2, поэтому 1 идёт в индекс 1; уменьшаем до 1. |
Разместить 4 | output = [_, 1, 2, 4, 4] | count[4] = 4, поэтому эта 4 идёт в индекс 3; уменьшаем до 3. |
Разместить 1 | output = [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
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))Код Counting Sort на JavaScript
1function countingSort(arr) {2 // Count occurrences of each value, then rebuild in order3 const max = Math.max(...arr);4 const count = new Array(max + 1).fill(0);5 for (const x of arr) count[x]++;6 const out = [];7 count.forEach((c, value) => {8 for (let k = 0; k < c; k++) out.push(value);9 });10 return out;11}12
13const data = [4, 2, 9, 2, 7, 4, 1, 4];14console.log("Before:", data);15console.log("Sorted:", countingSort(data));Код Counting Sort на Java
1import java.util.Arrays;2
3public class Main {4 static int[] countingSort(int[] arr) {5 int max = 0;6 for (int v : arr) max = Math.max(max, v);7 int[] count = new int[max + 1];8 for (int v : arr) count[v]++;9 // Prefix sums turn counts into final positions10 for (int i = 1; i <= max; i++) count[i] += count[i - 1];11 int[] out = new int[arr.length];12 // Walk backwards so equal values keep their order (stable)13 for (int i = arr.length - 1; i >= 0; i--) {14 out[--count[arr[i]]] = arr[i];15 }16 return out;17 }18
19 public static void main(String[] args) {20 int[] arr = {4, 2, 2, 8, 3, 3, 1};21 System.out.println("Before: " + Arrays.toString(arr));22 int[] sorted = countingSort(arr);23 System.out.println("After: " + Arrays.toString(sorted));24 }25}Код Counting Sort на C++
1#include <algorithm>2#include <iostream>3#include <vector>4
5void printVec(const std::vector<int>& a) {6 for (int x : a) std::cout << x << " ";7 std::cout << "\n";8}9
10void countingSort(std::vector<int>& a) {11 if (a.empty()) return;12 int maxVal = *std::max_element(a.begin(), a.end());13 // count[v] = how many times v appears14 std::vector<int> count(maxVal + 1, 0);15 for (int x : a) ++count[x];16 // Rebuild the array from the counts17 size_t idx = 0;18 for (int v = 0; v <= maxVal; ++v) {19 while (count[v]-- > 0) a[idx++] = v;20 }21}22
23int main() {24 std::vector<int> data = {4, 2, 2, 8, 3, 3, 1, 7};25 std::cout << "Before: ";26 printVec(data);27 countingSort(data);28 std::cout << "After: ";29 printVec(data);30 return 0;31}Код Counting Sort на C
1#include <stdio.h>2#include <stdlib.h>3
4void printArr(const int a[], int n) {5 for (int i = 0; i < n; i++) printf("%d ", a[i]);6 printf("\n");7}8
9void countingSort(int a[], int n) {10 int maxVal = a[0];11 for (int i = 1; i < n; i++) {12 if (a[i] > maxVal) maxVal = a[i];13 }14 // count[v] = how many times v appears15 int* count = calloc(maxVal + 1, sizeof(int));16 for (int i = 0; i < n; i++) count[a[i]]++;17 // Rebuild the array from the counts18 int idx = 0;19 for (int v = 0; v <= maxVal; v++) {20 while (count[v]-- > 0) a[idx++] = v;21 }22 free(count);23}24
25int main(void) {26 int data[] = {4, 2, 2, 8, 3, 3, 1, 7};27 int n = sizeof(data) / sizeof(data[0]);28 printf("Before: ");29 printArr(data, n);30 countingSort(data, n);31 printf("After: ");32 printArr(data, n);33 return 0;34}Частые вопросы о сортировке подсчётом
Какова временная сложность сортировки подсчётом?
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. Забыть про это смещение - распространённая ошибка, приводящая к сбою на отрицательных входных данных.