Sortowanie przez zliczanie (counting sort)
Ostatnia aktualizacja
Sortowanie przez zliczanie to sortowanie bez porównań dla liczb całkowitych ze znanego, ograniczonego zakresu. Zlicza, ile razy pojawia się każda wartość, a potem na podstawie tych liczników zapisuje każdą wartość bezpośrednio na jej miejscu w posortowanym wyniku, bez żadnych porównań. Kliknij odtwarzanie powyżej i zobacz, jak wartości są zliczane, a potem odkładane z powrotem w kolejności.
Sortowanie przez zliczanie działa w czasie O(n + k), gdzie k to zakres wartości wejściowych. Gdy k nie jest dużo większe od n, algorytm jest niezwykle szybki i może pokonać sortowania przez porównania o złożoności O(n log n), ale przy ogromnym zakresie wartości tablica liczników O(k) czyni go niepraktycznym.
Złożoność czasowa i pamięciowa
| Przypadek | Złożoność | Uwagi |
|---|---|---|
| Czas | O(n + k) | n elementów, k = zakres wartości |
| Pamięć | O(n + k) | Tablica liczników + tablica wynikowa |
| Stabilne | Tak | Przy umieszczaniu od prawej do lewej z sumami prefiksowymi |
| Porównania? | Nie | Sortuje przez zliczanie, nie przez porównywanie |
| Najlepsze dla | Mały zakres liczb całkowitych | k bliskie n |
Krok po kroku
| Krok | Co się dzieje |
|---|---|
| 1 | Znajdź wartość maksymalną, aby ustalić rozmiar tablicy liczników. |
| 2 | Policz, ile razy występuje każda wartość. |
| 3 | (Opcjonalnie) Zamień liczniki na sumy prefiksowe, aby zapewnić stabilność. |
| 4 | Zapisz każdą wartość w wyniku tyle razy, ile się pojawia. |
| 5 | Tablica wynikowa jest teraz w pełni posortowana. |
Przykład krok po kroku
Sortowanie [1, 4, 1, 2, 4] (wartości od 0 do 4, więc tablica liczników ma 5 pól):
| Przebieg | Stan | Działanie |
|---|---|---|
| Skanowanie wejścia | count = [0, 2, 1, 0, 2] | Zlicz wystąpienia: 1 pojawia się dwa razy, 2 raz, 4 dwa razy. |
| Sumy prefiksowe | count = [0, 2, 3, 3, 5] | Każde pole zawiera teraz liczbę wartości <= jego indeksowi, co daje pozycje końcowe. |
Umieść 4 | output = [_, _, _, _, 4] | Czytaj od prawej do lewej: count[4] = 5, więc 4 trafia pod indeks 4; zmniejsz do 4. |
Umieść 2 | output = [_, _, 2, _, 4] | count[2] = 3, więc 2 trafia pod indeks 2; zmniejsz do 2. |
Umieść 1 | output = [_, 1, 2, _, 4] | count[1] = 2, więc 1 trafia pod indeks 1; zmniejsz do 1. |
Umieść 4 | output = [_, 1, 2, 4, 4] | count[4] = 4, więc ta 4 trafia pod indeks 3; zmniejsz do 3. |
Umieść 1 | output = [1, 1, 2, 4, 4] | count[1] = 1, więc ta 1 trafia pod indeks 0. Tablica jest posortowana. |
Kiedy używać sortowania przez zliczanie
| Używaj, gdy | Unikaj, gdy |
|---|---|
| Sortujesz liczby całkowite (lub klucze, które da się na nie odwzorować) z małego, znanego zakresu. | Zakres wartości k jest dużo większy niż liczba elementów n. |
Potrzebujesz liniowego czasu O(n + k) i stać cię na dodatkowe tablice. | Pamięci jest mało: tablica liczników kosztuje O(k) niezależnie od n. |
| Potrzebujesz stabilnego sortowania jako podprocedury (np. w radix sort). | Klucze to liczby zmiennoprzecinkowe, napisy lub dowolne obiekty bez odwzorowania na liczby całkowite. |
| Wartość maksymalna jest ograniczona i łatwo ją wcześniej obliczyć. | Zakres jest nieznany lub nieograniczony, więc nie da się ustalić rozmiaru tablicy liczników. |
Counting Sort: kod
Przejrzysta, gotowa do uruchomienia implementacja algorytmu Counting Sort w językach: Python, JavaScript, Java, C++, C. Wybierz język, skopiuj kod albo otwórz go od razu w edytorze online Coddy.
Counting Sort: kod (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: kod (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: kod (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: kod (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: kod (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}Sortowanie przez zliczanie: najczęstsze pytania
Jaka jest złożoność czasowa sortowania przez zliczanie?
O(n + k), gdzie n to liczba elementów, a k to zakres możliwych wartości. Gdy k = O(n), jest to czas liniowy. Zużywa O(n + k) dodatkowej pamięci.Czy sortowanie przez zliczanie jest stabilne?
Może być. Wersja stabilna buduje sumy prefiksowe liczników i umieszcza elementy od prawej do lewej, co zachowuje względną kolejność równych kluczy. Prosta wersja "przepisz według wartości" pokazana tutaj daje poprawne sortowanie, ale stosuje się ją głównie do zwykłych liczb całkowitych.
Kiedy używać sortowania przez zliczanie?
k jest dużo większy niż liczba elementów, tablica liczników marnuje pamięć i lepsze jest sortowanie przez porównania.Czym różni się sortowanie przez zliczanie od sortowania pozycyjnego (radix sort)?
10 dla cyfr dziesiętnych). Radix sort radzi sobie z dużymi zakresami wartości, przy których pojedyncze sortowanie przez zliczanie byłoby niepraktyczne.Dlaczego sortowanie przez zliczanie nie zawsze jest szybsze od quicksorta?
O(n + k), więc wygrywa tylko wtedy, gdy zakres wartości k jest porównywalny z n. Jeśli k jest ogromne, na przykład przy sortowaniu 100 wartości z zakresu od 0 do 1,000,000,000, dominuje tablica liczników O(k) i marnuje pamięć, a sortowanie przez porównania O(n log n), takie jak quicksort, pozostaje szybkie i oszczędne.Czy sortowanie przez zliczanie obsługuje liczby ujemne?
value - min, aby najmniejsza wartość trafiała pod indeks 0. Rozmiar tablicy liczników wynosi wtedy max - min + 1. Pominięcie tego przesunięcia to częsty błąd, który powoduje awarię przy ujemnych danych.