Table of Contents
정렬 작업이 크게 배열하는 경우, 등급, 연령, 또는 카테고리 코드와 같은 작은 정수의 큰 배열을 포함하되 QuickSort 또는 MergeSort와 같은 고전적인 비교 기반 알고리즘은 오버킬처럼 느낄 수 있습니다. 이 알고리즘은 O (n log n) 시간에서 실행되지만 가능한 값의 범위가 제한되면 선형 O (n + k) 시간에서 정렬 할 수 있습니다 Counting Sort:]:]:]:]:]]:]]:]]:]:[FLT:]]]]]:[[FLT:]]]]]:]:]]:]:[[[[[FLT:]]]]]]]]]]]]:[[[[[[[[[[[[[[[[[[[[[[FLT:]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
분류 작업
계산 정렬은 입력 값이 작은 범위에서 그려진 정수가 ]인지 지식을 악용합니다. 쌍방향 비교 대신 값의 주파수 심화를 구축하고 올바른 정렬 위치에 각 요소에 배치하는 것을 사용하여 자신의 토로그램을 사용합니다.
기본 접근: 직접 재구성
Counting Sort의 가장 간단한 버전은 두 패스에서 작동합니다.
- Count frequencies – 입력 배열을 통해 입력하고 각 값에 대한 카운터를 입력합니다.
- ] 입력을 - 가장 작은에서 가장 큰 카운터 배열을 통해, 각 값에 대한, 입력 배열에 다시 쓰기 그것의 수로 많은 시간.
이 출력을 산출하지만 ]not는 중복의 상대적인 순서를 보존합니다 (정상하지 않습니다). 당신이 동등한 열쇠를 가진 기록의 본래 순서를 지키기 동안 열쇠에 분류할 때 안정성 사정. 다음을 묘사한 안정되어 있는 변종은, 연습에서 일반적으로 사용되는 1개입니다.
안정된 채식: 특이한 수
계산 정렬 안정을 만들기 위해, 우리는 세 번째 패스를 추가합니다 :
- 이전으로 주파수를 계산합니다.
- 수의 배열을 누적 수의 배열로 변환합니다. 이 단계 후, 는 요소의 수를 보유하고 ≤ i.
- 역방향 (마지막 요소에서 첫 번째)에 입력 배열을 결정합니다. 각 요소의 경우 출력 배열의 위치를 찾기 위해 누적 카운트를 사용하여, 그것을 배치하고, 카운트를 줄입니다.
우리는 역방향 때문에, 동일한 성분의 상대적인 순서는 보존됩니다. 산출 배열은 입력에서 분리됩니다, 그래서 이 버전은 산출을 위한 O (n) 추가 공간을, 기본 버전이 입력을 과잉해서 in-place를 분류할 수 있는 산출을 이용합니다.
C#에서 계산 정렬 구현
아래는 두 개의 C# 구현입니다. 기본 인스톱 버전 ( 안정성이 불필요한 시나리오) 및 보조 배열을 사용하는 안정적인 버전입니다. 둘 다 사전에 최대 값을 알고 있습니다.
기본(Non-Stable) 계산 정렬
이 변형은 추가 출력 버퍼없이 입력 어레이를 직접 정렬합니다. 그것은 메모리 ‐ 효율적이지만 안정적이지 않습니다.
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;
}
}
}
안정된 계수 정렬
안정적인 버전은 입력과 같은 크기의 출력 배열을 요구합니다. 또한 위치 요소에 누적 카운트를 올바르게 사용합니다.
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;
}
두 가지 구현에서 는 배열에 나타나는 가장 큰 정수입니다. 사실 최대가 알 수 없는 경우, 우선 검사 (O(n))로 계산할 수 있습니다. 안정된 버전은 새로운 정렬 배열을 반환하고, 원래 변경되지 않은 상태로 남겨두었습니다.
공정성 분석
n]은 요소의 수와 k]=최대 - 최소 + 1 (가능한 값의 범위).
- Time: Counting Sort run in ]O(n + k) time. Counting phase is O(n), 누적 접두사 O(k), 재건축은 O(n)입니다. k가 O(n)일 때 알고리즘은 선형입니다.
- Space: 기본 버전은 O(k)의 추가 공간을 사용하여 계산 배열을 위한 공간입니다. 안정적인 버전은 O(n + k)를 사용하여 출력 배열을 할당합니다. 이 범위가 많은 항목에 비해 계산되지 않는 종류가 있습니다.
- 다른 종류와 비교: QuickSort와 MergeSort와 같은 비교 ‐는 적어도 O(n log n) 비교를 요구합니다. 작은 k (e.g., k < 10,000 및 n > 100,000)의 경우, 분류는 더 빨리 확대의 순서일 수 있습니다.
변리사 및 확장
처리 부정 적
기본으로 비 부정적인 정수와 함께 정렬 정렬. 부정적인 값을 처리하려면 전체 범위를 이동하므로 최소한 0이됩니다. 예를 들어, 숫자 범위가 -1000에서 1000으로 설정하면 각 요소의 +1000로 오프셋됩니다. 카운트 배열은 크기 ]를 가지고 있습니다.
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;
}
비 정수 키 매핑
계산 정렬은 정수 키가 필요합니다. 데이터가 문자 (바이트) 또는 정수로 캐스팅 될 수있는 열렬한 구성이면 여전히 적용 할 수 있습니다. 더 큰 개체의 경우 정수 키를 추출하고 객체를 정렬 할 수 있습니다. 이것은 정확히 어떻게 Radix Sort이 종종 계산 정렬을 사용하여 내부 서브로 정렬을 사용합니다.
Radix 분류 콤보
Radix Sort 프로세스 자리 (또는 비트) 개별적으로, 그리고 카운트 정렬은 기본 (예 : 10 또는 256)이 작을 때 각 패스에 대한 자연 선택입니다. 이것은 선형 정렬을 허용하고 임의 정수, 단지 작은 것.
C#의 실제적인 고려
메모리 발자국 및 대형 k
가장 큰 pitfall은 사용할 수있는 메모리보다 큰 계산 배열을 할당합니다. 예를 들어, 1,000,000의 폐기물 공간과 1,000 요소 정렬. 항상 k가 ]]n]]]보다 큰 크기의 주문을하지 않습니다. 다른 하나는 비교 종류 또는 하이브리드 접근을 사용합니다.
평행 및 Span< T>
매우 큰 배열을 위해, 당신은 스레드의 맞은편에 입력을 분할하여 계산 단계를 평행시킬 수 있습니다. 각 실은 개인 배열으로 그것의 세그먼트를 조사하고, 그 후에 부분 결과는 집계됩니다. 를 사용하여 그리고 ]는 범위를 작을 때 계산 배열을 힙 할당을 감소시킬 수 있습니다.
Edge 케이스
- Empty array – 즉시 반환합니다.
- 단일 요소 – 분류는 삼극관이다.
- 모든 동일한 값 – 카운트 어레이에는 1개의 비제로 엔트; 재구성은 O(n)에서 실행됩니다.
- 대용량이지만, 비소 데이터 – 대부분의 카운트 항목이 0이기 때문에 정렬이 증가합니다. 해시 기반 계산 접근 또는 버킷 정렬을 고려하십시오.
의약철강
입력 정수가 작은 범위로 떨어지는 경우 계산 정렬 (예 : 0-100, 0-120, 0-255, 오류 코드 0-255). 큰 범위의 경우 Radix Sort 또는 하이브리드를 고려하여 QuickSort로 높은 범위 파티션을 위해 다시 떨어뜨립니다.
계산을 할 때 정렬 (및 때에)
| Situation | Recommendation |
|---|---|
| Small integer range (k ~ n) | Excellent choice – linear time, simple code. |
| Large integer range (k >> n) | Avoid – memory waste and O(k) overhead. |
| Need stability | Use the stable variant (cumulative counts). |
| Strings or objects | Consider Radix Sort or a comparison sort. |
| Extremely large datasets | Counting Sort can be parallelized; but watch memory. |
벤치마킹 및 성능
n = 1,000,000 및 k = 1,000의 전형적인 벤치 마크에서, 분류를 계산하는 것은 (인트로소트를 사용하는) 시간의 약 20-30 %에 완료합니다. k 감소로 갭 넓습니다. 아래는 .NET 8과 현대 CPU의 대략적인 비교 (실행 시간)입니다:
n = 1,000,000 | k = 1,000
Array.Sort (QuickSort variant) : 68 ms
CountingSortBasic : 12 ms
CountingSortStable : 18 ms
범위가 10,000으로 성장할 때, 정렬을 계산하는 것은 여전히 승리하지만, 마진 좁은. k = 100,000의 메모리 오버 헤드 (수량 배열의 경우 ≈ 400 KB)는 CPU 캐시를 제거하고 성능이 저하 될 수 있습니다.
관련 기사
계산 정렬은 데이터가 제약에 맞는 때 선형 성능을 제공하는 deceptively 간단한 알고리즘입니다. C # 개발자들은 작은 정수의 큰 배열을 다루는, 그것은 극적으로 정렬 시간을 줄일 수있는 귀중한 도구입니다. 데이터의 범위에 눈을 유지하십시오. 작고 알려진 경우, 계산 정렬은 비교 기반 대안을 아웃합니다. 더 일반적인 목적 분류를 위해, 내장 된 를 사용하지만, 항상 줄을 드롭 할 때 항상 숫자를 드롭 할 준비가되어 있습니다.
더 읽기를 위해 ]Wikipedia 기사를 참조하는 분류], Microsoft docs on Array.Sort], GeeksforGeeks.