Khi nhiệm vụ sắp xếp của bạn bao gồm một loạt số nguyên nhỏ, số nhiều, tuổi, hoặc mật mã phân loại - các thuật toán sử dụng so sánh cổ điển như QuickSort hoặc ert có thể cảm thấy bị giết. Những thuật toán này chạy trong thời gian O(n log n) thời gian, nhưng nếu phạm vi của giá trị có thể là giới hạn, bạn có thể sắp xếp trong thời gian [FL: 0] thời gian [FL: 0] Sắp xếp [FLT]. Việc sắp xếp [FL:1]. Thuật toán không tương ứng này có lợi thế cho phép tính các yếu tố có thể xảy ra, bạn có thể tính các yếu tố không phải là một dạng ổn định và cả hai quyền sử dụng nhanh.

Làm thế nào tính việc sắp xếp?

Tính

Cách tiếp cận cơ bản: Trực tiếp hồi phục

Phiên bản đơn giản nhất của Sắp xếp đếm hoạt động trong hai lần đi qua:

  1. Tần số – Nó chạy qua các dòng đầu vào và tăng dần một máy đếm cho mỗi giá trị bạn thấy.
  2. Vượt quá việc ghi – đi qua các dãy đối chiếu từ nhỏ nhất đến lớn nhất và, cho mỗi giá trị, ghi nó trở lại vào các dòng nhập nhiều lần như số đếm của nó.

Điều này mang lại kết quả đã sắp xếp nhưng không [FLT: 0] [FLT: 1] bảo tồn thứ tự tương đối của bản sao (nó không ổn định). Tính bền vững là khi bạn sắp xếp trên phím, trong khi giữ thứ tự gốc của tài liệu bằng. Biến thể ổn định, được miêu tả tiếp theo, là thứ tự thường dùng nhất trong thực tế.

Sự đa dạng có thể được: Số đếm được chữa lành

Để làm cho việc đếm được ổn định, chúng ta thêm một lần thứ ba:

  1. Đếm tần số như trước.
  2. Biến dãy tần số thành dãy đếm tích lũy. Sau bước này, giữ số yếu tố [FLT: 0]i .
  3. Hãy lặp lại dãy nhập theo chiều ngược (từ yếu tố cuối cho đến bước đầu tiên). Đối với mỗi yếu tố, hãy dùng số đếm tích lũy của nó để tìm vị trí của nó trong dãy kết xuất, đặt nó và giảm số đếm.

Bởi vì chúng ta đi ngược lại, thứ tự tương đối của các nguyên tố bằng được bảo tồn.

Phân loại đếm trong C#

Dưới đây là hai C# thực hiện: phiên bản ở nơi (cho những tình huống không cần thiết) và phiên bản ổn định sử dụng một dãy phụ trợ. Cả hai đều yêu cầu biết giá trị tối đa trước.

Cơ bản (không có) Sắp xếp đếm

Biến thể này sắp xếp trực tiếp các mảng nhập mà không cần bộ đệm xuất thêm. Nó là bộ nhớ hiệu quả nhưng không ổn định.

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;
 }
 }
}

Sắp xếp đếm được

Phiên bản ổn định đòi hỏi một chuỗi xuất cùng kích cỡ với dữ liệu nhập. Nó cũng dùng tích lũy để định vị các phần tử đúng.

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;
}

Trong cả hai việc thực hiện, là số nguyên lớn nhất xuất hiện trong mảng. Nếu tối đa thật là không rõ, bạn có thể tính toán nó với một bản quét chuẩn (O(n). Phiên bản ổn định trả về một dãy mới, để lại bản gốc không thay đổi.

Phân tích độ phức tạp

Hãy n là số nguyên tố và = tối đa – min + 1 (tải giá trị có thể).

  • Thời gian :) Sắp xếp đếm chạy ) O(n + k] . Thời gian đếm là O(n), tiền tố tích lũy là O(k), và sự tái tạo là O(n). Khi k là O(n), thuật toán tuyến tính.
  • [FLT: 0] Không gian [FLT: 1] phiên bản cơ bản dùng thêm O (k) cho mảng đếm. Phiên bản ổn định dùng O(n + k) vì nó cũng phân bổ các mảng xuất. Tính năng này sẽ không thích hợp khi phạm vi lớn tương ứng với số mục.
  • Máy tính đáp ứng với các loại khác: so sánh các loại dựa trên QuickSort và Port yêu cầu ít nhất O(n log n). Đối với k (v. d., k < 10,000 & ng; 100.000), sắp xếp đếm có thể là thứ tự nhanh hơn.

Biến thế và mở rộng

Xử lý các nguyên âm

Tính Sắp xếp theo bản địa hoạt động với số nguyên không phải là số âm. Để xử lý giá trị âm, thay đổi toàn bộ phạm vi tối thiểu thành số không. Ví dụ, nếu số từ - 1000 đến 1000, bù đắp cho mỗi phần tử 60.000. Bảng số thì có kích cỡ .

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;
}

Phím không mã nguồn điện

Sắp xếp đếm cần thiết phím số nguyên. Nếu dữ liệu của bạn bao gồm các ký tự (tes), hoặc các số đếm có thể được đúc cho các số nguyên, bạn vẫn có thể áp dụng nó. Đối với đối tượng lớn hơn, bạn có thể lấy một phím số nguyên và sắp xếp các đối tượng theo đó là cách mà Radix Sắp xếp các bộ đếm như là các tập hợp phụ trong của nó.

Radix sắp xếp Combo

Radix sắp xếp chữ số (hoặc bit) riêng, và sắp xếp đếm là sự lựa chọn tự nhiên cho mỗi lần đi qua (v. d., 10 hay 257). Tính năng này cho phép sắp xếp các số nguyên tùy ý, không chỉ số nhỏ.

Những sự cân nhắc thực tế ở C#

In chân trang và k lớn

Độ sâu lớn nhất là cách ly một dãy số lớn hơn bộ nhớ có sẵn. Chẳng hạn, sắp xếp 1.000 yếu tố với khoảng không dài 1.000.000 rác. Luôn xác nhận rằng [FLT: 0] [FLT: 0] [FLT: 1] không phải là thứ tự của độ lớn hơn [FLT:] .

Chủ nghĩa song song và Span< T>

Đối với các dãy cực lớn, bạn có thể tương ứng với giai đoạn đếm bằng cách chia các phần nhập qua các sợi. Mỗi sợi đếm đoạn thành một dãy riêng, và kết quả phần được tổng hợp lại. Dùng và cho dãy số đếm có thể giảm sự định vị ngăn chặn chất khi phạm vi nhỏ.

& Sắp xếp:

  • mảng ) – trở lại ngay lập tức.
  • Thành phần tiếng hát [FLT: 1] — sắp xếp là tầm thường.
  • Tất cả các giá trị giống hệt nhau – dãy số có một mục nhập không phải là số không; tái tạo chạy trong O(n).
  • nhưng dữ liệu mơ hồ ) – Sắp đếm biến không hiệu quả vì phần lớn mục nhập là số không. Hãy xem phương pháp đếm theo lô- ni- a [FLT: 1] – Sắp xếp đếm không hiệu quả vì phần lớn mục số là số không. Hãy xem xét phương pháp đếm theo hah bằng cách tiếp cận hoặc Bucket.

Khuyên hiệu suất

Dùng Bộ đếm khi bạn biết các số nguyên nhập sẽ rơi vào phạm vi nhỏ (v. d., điểm 0–100, độ tuổi 0–120, hoặc mã lỗi 0–255). Đối với phạm vi lớn hơn, hãy xem xét Radix Sắp xếp hoặc một người lai rơi về QuickSort cho phân vùng ở mức độ cao.

Khi nào nên dùng sắp xếp đếm (và khi không nên)

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.

Comment

Trong một dấu chấm nhỏ điển hình với n = 1.000.000 và k = 1000, sắp xếp đếm hoàn tất trong khoảng 20–30% thời gian được [FLT: 9) (dùng nội dung). Khoảng cách mở rộng là k giảm. Dưới đây là một so sánh xấp xỉ (bắt đầu trên CPU hiện đại với .NET):

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

Khi phạm vi tăng lên 10.000, thì sắp xếp đếm vẫn thắng, nhưng các lề thu hẹp. Đối với k = 100.000, bộ nhớ trên đầu (Graft 400 KB cho dãy số) bắt đầu làm tổn thương bộ nhớ tạm CPU, và hiệu suất có thể giảm thiểu.

Kết luận

Sắp xếp đếm là một thuật toán đơn giản để cung cấp hiệu suất tuyến tính khi dữ liệu khớp với các hạn chế. Đối với các nhà phát triển C# xử lý một dãy số nguyên nhỏ, nó là một công cụ giá trị có thể giảm đáng kể thời gian sắp xếp. Giữ mắt trên phạm vi dữ liệu của bạn: nếu nó nhỏ và được biết, sắp xếp đếm sẽ vượt quá bất kỳ thay thế dựa trên mã nguồn rộng. Đối với nhiều mục đích khác, hãy sử dụng các thiết lập [FL:11], nhưng luôn luôn sẵn sàng để đếm khi dòng lên và theo nghĩa bóng.

Để đọc thêm, hãy tham khảo ý kiến các bác sĩ bài báo về bộ đếm , ) )Microsoft [Fray.Sort , và một hướng dẫn thực tế từ Gekks [FL: 5.].