Ketika tugas pengurutan Anda melibatkan array besar dari integer kecil ⁇ seperti nilai, umur, atau kode kategori ⁇ jenis algoritma berbasis perbandingan klasik seperti QuickSort atau GageSort dapat terasa seperti overkill. Algoritma ini berjalan dalam waktu O(n log n), tetapi jika rentang nilai yang mungkin terbatas, Anda dapat mengurutkan dalam waktu linear O(n + k) dengan Counting Sort]. Algoritme non-komparasi ini mensortasi fakta yang dapat Anda hitung daripada membandingkan elemen yang stabil dan baik secara cepat untuk masukan yang benar.

Berkarya dengan Cara Menghitung Jenis

Urutan Penghitungan ward mengeksploitasi pengetahuan bahwa nilai masukan adalah integer yang diambil dari kisaran kecil . Alih-alih perbandingan sepasang, ia membangun histogram frekuensi nilai dan kemudian menggunakan histogram tersebut untuk menempatkan setiap elemen dalam posisi yang diurutkan dengan benar.

Pendekatan Dasar: Rekonstruksi Langsung

Versi paling sederhana dari Counting Sort bekerja dalam dua tahap:

  1. Kurang frekuensi[ ⁇ Iterate melalui array input dan increament a counter untuk setiap nilai yang anda lihat.
  2. [[EANFAILT:0]]Overwrite input ⁇ Berjalan melalui array counter dari yang terkecil ke yang terbesar dan, untuk setiap nilai, menulis kembali ke dalam input array sebanyak kali jumlah.

Ini tools menghasilkan keluaran yang diurutkan tetapi melakukan not[ memelihara urutan relatif duplikat (ia tidak stabil). Stability hal ketika Anda mengurutkan pada kunci sambil menjaga urutan asli catatan dengan kunci yang sama. Varian stabil, dijelaskan selanjutnya, adalah yang paling umum digunakan dalam praktik.

Varian yang Stabil: Kumulatif

Untuk membuat Counting Sort stabil, kita menambahkan pertigaan:

  1. Frekuensi seperti sebelumnya.
  2. Jelmakan nigfan frekuensi menjadi array hitungan kumulatif. Setelah langkah ini, memegang jumlah elemen ⁇ [3]]i.
  3. ¡Oadechi Iterate input array dalam terbalik (dari elemen terakhir ke pertama). Untuk setiap elemen, gunakan hitungan kumulatifnya untuk menemukan posisinya dalam array output, tempatkan, dan pengurangan hitungan.

Karena kita traverse terbalik, urutan relatif dari elemen yang sama dipertahankan.Array output terpisah dari input, sehingga versi ini menggunakan ruang tambahan O(n) untuk output, sedangkan versi dasar dapat mengurutkan in-place dengan menimpa masukan.

Implementasi yang Menghitung Jenis C#

Di bawah ini adalah dua implementasi C#: versi dasar in-place (untuk skenario di mana stabilitas tidak diperlukan) dan versi stabil yang menggunakan array tambahan. Keduanya membutuhkan mengetahui nilai maksimum di muka.

Dinilai Dasar (Non ⁇ Stable) Penghitungan Jenis

Varian ini mengurutkan input array secara langsung tanpa buffer output ekstra. Ini adalah memori ⁇ efisien tetapi tidak stabil.

public static void CountingSortBasic(int[] array, int maxValue)
{
 int[] counts = new int[maxValue + 1];

 // Count each element's frequency
 for (int i = 0; i < array.Length; i++)
 {
 counts[array[i]]++;
 }

 // Overwrite the original array in sorted order
 int index = 0;
 for (int value = 0; value <= maxValue; value++)
 {
 while (counts[value]-- > 0)
 {
 array[index++] = value;
 }
 }
}

Urutan Penghitungan yang Stabil

Versi stabil membutuhkan array output dengan ukuran yang sama dengan input. Ini juga menggunakan kumulatif dihitung untuk memposisikan elemen dengan benar.

public static int[] CountingSortStable(int[] array, int maxValue)
{
 int[] counts = new int[maxValue + 1];
 int[] output = new int[array.Length];

 // Step 1: Count occurrences
 foreach (int num in array)
 {
 counts[num]++;
 }

 // Step 2: Transform counts to cumulative counts
 for (int i = 1; i <= maxValue; i++)
 {
 counts[i] += counts[i - 1];
 }

 // Step 3: Build the output array (iterate input in reverse for stability)
 for (int i = array.Length - 1; i >= 0; i--)
 {
 int value = array[i];
 output[counts[value] - 1] = value;
 counts[value]--;
 }

 return output;
}

Dalam kedua implementasi, adalah integer terbesar yang muncul dalam array. Jika maksimum sejati tidak diketahui, anda dapat menghitungnya dengan scan preparatory (O(n)). Versi stabil mengembalikan array yang diurutkan baru, meninggalkan aslinya tidak berubah.

Analisis Kompleksitas yang Berkompleks

HANOL]n menjadi jumlah unsur dan k = max ⁇ min + 1 (jangkauan nilai yang mungkin).

  • [[ZOZT:0]]Time: Penghitungan Urut berjalan dalam O(n + k) waktu. Fase Penghitungan adalah O(n), awalan kumulatif adalah O(k), dan rekonstruksi adalah O(n). Ketika k adalah O(n), algoritma adalah linear.
  • [5][EU]AZOFLT:0]]Space: Versi dasar menggunakan O(k) ruang ekstra untuk array hitungan. Versi stabil menggunakan O(n + k) karena juga mengalokasikan array output. Hal ini membuat Counting Sort tidak sesuai ketika jangkauan yang besar relatif terhadap jumlah item.
  • [[ObletarT:0]]Comparison dengan macam-macam lain: Perbandingan ⁇ berbasis macam seperti QuickSort dan CangeSort memerlukan perbandingan setidaknya O(n log n). Untuk k kecil (misalnya, k < 10.000 dan n > 100.000), Counting Sort dapat menjadi perintah magnitudo lebih cepat.

Variasi dan Ekstensi

Mengeluarkan Integer Negatif

Penghitungan Urutan secara native bekerja dengan integer non ⁇ negatif. Untuk menangani nilai negatif, geser seluruh jangkauan sehingga minimum menjadi nol. Misalnya, jika angka berkisar dari -1000 hingga 1000, ofset setiap elemen dengan +100. Tataran hitung kemudian memiliki ukuran .

public static int[] CountingSortWithNegative(int[] array)
{
 if (array.Length == 0) return array;

 int min = array.Min();
 int max = array.Max();
 int range = max - min + 1;

 int[] counts = new int[range];
 int[] output = new int[array.Length];

 foreach (int num in array)
 counts[num - min]++;

 for (int i = 1; i < range; i++)
 counts[i] += counts[i - 1];

 for (int i = array.Length - 1; i >= 0; i--)
 {
 int value = array[i];
 output[counts[value - min] - 1] = value;
 counts[value - min]--;
 }

 return output;
}

Pemetaan Berencana Bukan Integer Keys

Urutan Penghitungan Urutan diperlukan tombol integer. Jika data Anda terdiri dari karakter (byte), atau enumerasi yang dapat dicast ke integer, Anda masih dapat menerapkannya. Untuk objek yang lebih besar, Anda dapat mengekstrak kunci integer dan mengurutkan objek menurut ⁇ ini adalah persis bagaimana Penyisihan Radix sering menggunakan Penghitungan Sort sebagai subroutine dalamnya.

Fobia Radix Sort Combo

Proses urut Radix diaz (atau bit) secara individual, dan Penghitungan Sort adalah pilihan alami untuk setiap lulus ketika basa (mis., 10 atau 256) berukuran kecil. Ini memungkinkan pengurutan linear ⁇ waktu integer arbitrari, bukan hanya yang kecil.

Konspeksi Praktis dalam C#

Memori Memori Memori Jejak kaki dan besar k

Aperson pitfall terbesar adalah mengalokasikan array hitung yang lebih besar dari memori yang tersedia. Sebagai contoh, mengurutkan 1.000 elemen dengan rentang ruang buang 1.000.000. Selalu pastikan bahwa k bukan perintah magnitudo lebih besar dari n[ ⁇ jika tidak menggunakan perbandingan sort atau pendekatan hibrida.

Selarionisme dan Span<T>

Untuk array yang sangat besar, Anda dapat mengsejajarkan fase penghitungan dengan memperatakan input melintasi threads. Setiap thread menghitung segmennya menjadi array privat, dan kemudian hasil parsialnya dirangkum. Menggunakan dan untuk array penghitungan dapat mengurangi alokasi tumpukan ketika jangkauannya kecil.

Kasus Pinggiran Besar Luar Biasa

  • Empty array ⁇ kembali segera.
  • [[NOLT:0]] Unsur siring ⁇ penyortiran adalah sepele.
  • [[ZANFAIL:0]]All value identik ⁇ array hitung memiliki satu masukan non ⁇ nol; rekonstruksi berjalan dalam O(n).
  • [[EfolfanFLT:0]] Jangkauan besar tetapi data jarang ⁇ Penghitungan Sort menjadi tidak efisien karena kebanyakan entri hitungan adalah nol. Pertimbangkan pendekatan perhitungan berbasis hash ⁇ based atau Bucket Sort.

Saran Kinerja yang Murah

Takibel Guna Penghitungan Sort ketika Anda tahu integer masukan jatuh ke dalam kisaran kecil (misalnya, nilai 0 ⁇ 100, umur 0 ⁇ 0, atau kode kesalahan 0 ⁇ 5). Untuk jangkauan yang lebih besar, pertimbangkan Radix Sort atau hibrida yang jatuh kembali ke QuickSort untuk partisi jarak tinggi ⁇ jauh.

Dicari ketika Menggunakan Jenis Penghitungan (dan Bila Tidak Ke)

SituationRecommendation
Small integer range (k ~ n)Excellent choice – linear time, simple code.
Large integer range (k >> n)Avoid – memory waste and O(k) overhead.
Need stabilityUse the stable variant (cumulative counts).
Strings or objectsConsider Radix Sort or a comparison sort.
Extremely large datasetsCounting Sort can be parallelized; but watch memory.

Prestasi dan Prestasi yang Menandakan

Dalam benchmark khas dengan n = 1.000.000 dan k = 1.000, Counting Sort selesai dalam sekitar 20 ⁇ 30% dari waktu yang diambil oleh (yang menggunakan introsort). Kelebaran gambang sebagai k berkurang. Di bawah ini adalah perbandingan perkiraan (kali eksekusi pada CPU modern dengan .NET 8):

n = 1,000,000 | k = 1,000
Array.Sort (QuickSort variant) : 68 ms
CountingSortBasic : 12 ms
CountingSortStable : 18 ms

Saat kisaran bertambah menjadi 10.000, Counting Sort masih menang, tetapi marginnya menyempit. Untuk k = 100.000, memori overhead ( ⁇ 400 KB untuk larik hitung) mulai melukai cache CPU, dan kinerja dapat menurun.

Kekecualian Kesimpulan

Sekualing Urutan yang bersifat menipu adalah algoritme sederhana yang menyampaikan kinerja linier ketika data sesuai dengan batasannya. Bagi pengembang C# yang berurusan dengan array besar bilangan bulat kecil, itu adalah alat yang secara dramatis dapat mengurangi waktu pengurutan. Tetap awasi jangkauan data Anda: jika itu kecil dan diketahui, Counting Sort akan melebihi perbandingan apapun ⁇ berdasarkan alternatif. Untuk penyortiran yang lebih umum ⁇ tujuan, gunakan [[[inFLT:11]], tetapi selalu siap untuk drop in Counting Sort ketika baris angka naik ⁇ secara harfiah dan kiasan.

Untuk pembacaan lebih lanjut, berkonsultasi dengan Wikipedia artikel tentang Counting Sort, Microsoft docs on Array.Iort[, dan panduan praktis dari GeeksforGeeks.