티스토리 뷰

알고리즘

Counting Sort

4567은 소수 2026. 2. 28. 16:17

정렬 방식 중 범위가 한정되어 있을 때 유용한 Counting Sort (개수 정렬) 에 대해 정리하고자 한다.

 

일반적으로 Quick Sort, Merge Sort 등은 O(n log n) 복잡도를 갖지만, Counting Sort 는 O(n+k) 를 갖는다. 

여기서 n은 배열 크기, k는 최대값이다. 

최대값이 복잡도에 포함되는 이유는 Counting Sort 원리를 이해하면 알 수 있고, 복잡도 계산도 어렵지 않다. 

 

Counting Sort 원리는 다음과 같다. 

 

현재 배열이 다음과 같은 10개 숫자로 이루어진 상황이라면, 

1 2 5 1 3 9 7 1 2 5

 

이들의 개수를 먼저 구한다. -> 단순 카운트이므로 O(n)

1 : 3개

2 : 2개

3 : 1개

5 : 2개

7 : 1개

9 : 1개

 

이후 이 개수들의 누적합을 구한다. -> 누적합 계산 시, 중간에 빈 값이더라도 최대값까지는 반복해야 하므로 O(k)

~1 : 3개

~2 : 5개

~3 : 6개

~5 : 8개

~7 : 9개

~9 : 10개

 

위 과정이 사실 상 전부이다. 왜냐하면 누적합을 계산함으로써, 1은 3번째 숫자까지 있다, 2는 5번째 숫자까지 있다, ..., 9는 10번째 숫자까지 있다. 라는 의미이기 때문이다. 

 

이제 기존 배열을 순차적으로 탐색하며 누적합 결과를 index로 처리 후, 누적합 -1을 해주면 남은 숫자들에 대해 index 계산을 반복할 수 있다.

(-1을 함으로써 이제 남은 숫자 중 2는 4번째까지 있을 수 있다. 라는 의미)

 

원래 배열 : 1 2 5 1 3 9 7 1 2 5

1 => 누적합 배열 3이므로, 3번째에 1 => ? ? 1 ? ? ? ? ? ? ? => 누적합 배열 1 값에 -1 => 누적합 배열 ~1 : 2개

2 => 누적합 배열 5이므로, 5번째에 2 => ? ? 1 ? 2 ? ? ? ? ? => 누적합 배열 2 값에 -1 => 누적합 배열 ~2 : 4개

5 => 누적합 배열 8이므로, 8번째에 5 => ? ? 1 ? 2 ? ? 8 ? ? => 누적합 배열 5 값에 -1 => 누적합 배열 ~5 : 7개

.... 이를 반복한다. 

 

원리는 위와 같고, 원리에서 알 수 있듯이, countable한 경우에 대해서만 가능하며, 숫자 범위가 한정된 경우에서만 quick sort 등보다 빠르다. 

(ex. 숫자는 1000개인데, 숫자 범위가 -1억 ~ 1억 이라면 누적합 계산을 위해 크기 2억짜리 배열 필요 + 계산 시에도 누적합 루프 계산 2억번 수행)

 

아래는 예시 코드이며, 범위가 좁지만 실제 최대값이 큰 경우 (ex. 1억 5천 ~ 1억 6천 사이 값을 갖는다 등) 보정을 위해 원본 배열에서 최소값을 뺀을 기준으로 누적합을 계산하였다.

(누적된 개수를 기준으로 몇 번째인지를 구하는 것이 핵심이므로, 불필요하게 메모리 쓸 이유 없음)

#include <iostream>

using namespace std;

static unsigned long long seed = 12345;
static int pseudo_rand(void)
{
	seed = seed * 123456789ULL + 123ULL;
	return (seed >> 16) & 0x3fffffff;
}

// counting sort 복잡도 : O(n + k) 
// n : 배열 크기 
// k : 최대값 
// 정렬 대상 범위가 한정된 경우 + countable 할 때만 유용 

const int N = 100;
int MAX_NUM;
int maxNum;
int minNum;

int arr[N]; // 원본 배열
int sorted[N]; // 최종 정렬 결과 

int *counts = nullptr;
int *countSum = nullptr;

void init() {
    // 단순 초기화
    if(counts != nullptr) delete[] counts;
    if(countSum != nullptr) delete[] countSum;

    MAX_NUM = pseudo_rand() % 100 + 1;

    for(int i = 0; i < N; i++) {
        arr[i] = pseudo_rand() % MAX_NUM;
        int tmp = pseudo_rand();
        if(tmp & 1) arr[i] *= -1;
    }
}

void getMinMaxNum() {
    maxNum = -987654321;
    minNum = 987654321;
    for(int i = 0; i < N; i++) {
        if(arr[i] > maxNum) maxNum = arr[i];
        if(arr[i] < minNum) minNum = arr[i];
    }
}

void makeCounts() {
    // 누적합 계산 
    // 최소값 기준으로 배열 값 뺀 뒤 누적합 계산 (최대값 기준 누적합 배열 메모리 최소화 위함)
    // 2 ~ 5 까지 수를 정렬해야 하면, 0 ~ 3 까지 수를 정렬한 뒤 +2 하는 것과 동일 
    int diff = maxNum - minNum;

    counts = new int[diff + 1](); // 0 ~ diff 만큼 저장 필요, 0 초기화
    countSum = new int [diff + 1]();

    // 값 별 개수 계산
    for(int i = 0; i < N; i++) {
        int num = arr[i] - minNum;
        counts[num]++;
    }

    // 누적합 계산 
    countSum[0] = counts[0];
    for(int i = 1; i <= diff; i++) {
        countSum[i] = countSum[i-1] + counts[i];
    }
}

void countingSort() {
    // counting sort 계산 
    for(int i = 0; i < N; i++) {
        int num = arr[i] - minNum;
        int idx = countSum[num] - 1; // 개수는 1부터 시작하므로 배열 index 맞추기 위해 -1
        
        sorted[idx] = arr[i]; // 정렬 위치에 넣기
        countSum[num]--; // 누적합에서 개수 빼기 
    }
}

#include <algorithm>

int main() {
    init();
    getMinMaxNum();
    makeCounts();
    countingSort();

    // 원본 베열
    cout << "origin array \n";
    for(int i = 0; i < N; i++) {
        cout << arr[i] << " ";
    }
    cout << "\n\n";

    // 정렬된 배열 
    cout << "sorted array \n";
    for(int i = 0; i < N; i++) {
        cout << sorted[i] << " ";
    }
    cout << "\n\n";

    // 정렬 올바른지 체크용
    int tmp[N] = {0, };
    memcpy(tmp, arr, sizeof(int) * N);
    sort(tmp, tmp + N);

    bool success = true;
    for(int i = 0; i < N; i++) {
        if(tmp[i] != sorted[i]) {
            success = false;
            break;
        }
    }
    if(success) cout << "Sorting Success !!\n";
    else cout << "Sorting Fail !!\n";
}

 

 

'알고리즘' 카테고리의 다른 글

최대유량 / 이분매칭 관련 정리  (0) 2025.03.03
최대 유량 문제  (0) 2025.02.02
36진법 덧셈, 절대값 뺄셈, 곱셈  (0) 2024.10.04
C++ lower_bound, upper_bound  (0) 2024.06.22
C++ map, set struct에 대해 만들기  (0) 2024.06.22
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
TAG
more
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함