Counting Sort ( 계수 정렬 )

계수 정렬은 삽입, 버블, 선택, 퀵, 합병 정렬들과 같이 비교를 수행하는 방식이 아닌 비교를 하지 않는 Non-Comparisions Sorting Algorithm 이다.

그러면 여기서 값을 정렬하는데 어떻게 비교 없이 수행하나요? 와 같은 질문이 있을 텐데, 계수 정렬은 비교 대신 정렬할 수의 개수와 배열의 인덱스를 가지고 정렬을 수행하게 된다.


1. 기본적인 흐름

2 1 2 4 5 3 6 5 3 을 정렬하고자 한다면

1의 개수는 1개, 2의 개수는 2개, 3의 개수는 2개, 4의 개수는 1개, 5의 개수는 2개, 6의 개수는 1개 이기 때문에 작은 수 부터 개수만큼 차례대로 나열하게 되면

1 2 2 3 3 4 5 5 6 으로 정렬이 된다.


2. 정렬 과정

  1. 정렬할 배열 A, 추가 배열 C를 생성
  2. 배열 C는 모든 값을 0으로 초기화
  3. 배열 A의 값을 토대로 배열 C의 인덱스값을 참조하여 값을 1씩 증가 (예를 들어 배열 A의 값중 3이 있다고 한다면, C의 3번째 인덱스 값을 1더해준다.)
  4. 배열 c의 각 값들을 직전 값을 더해 업데이트
    (예를 들어, 배열 C가 1,0,2,2 였다면, 1,1,3,5로 업데이트)
  5. 배열 C는 배열 A의 값들의 인덱스 값이므로, 배열 A를 끝에서부터 역순으로 훑으면서 배열 B에 정렬. (이때, 한 값을 B에 옮겨주었다면, 해당하는 인덱스의 배열 C의 값은 1을 빼준다.)

Note

여기서 배열 A의 뒤의 값부터 역순으로 훑으면서 정렬하는 이유는 counting sort 는 stable한 특성을 갖고있는 정렬인데, 앞에서부터 정렬을 하게 되면 stable한 특성이 깨지기 때문이다.


예를들어, 아래와 같은 배열 A가 있다면 원래 방법대로 뒤에서부터 훑게 되면 아래와 같이 정렬이 된다.

   A = { 3 5(a) 1 4 5(b) 2 }
   C = { 1 2 3 4 6 } 이 되어,

   A = { 1 2 3 4 5(a) 5(b) }

반대로 똑같은 A를 앞에서 부터 훑게 되면 아래와 같이 나오게 되면서 stable한 특성이 깨지게 된다.

    1 2 3 4 5(b) 5(a)

따라서 뒤에서 부터 훑으며 정렬을 하게 된다.


3. c++ 구현 코드

#include <iostream>
#include <cstdlib>  //time
#include <ctime>    //rand, srand
#include <string.h> //memset
#include <time.h>   //시간 측정

using namespace std;

#define MAX_ARRAY_SIZE 64   //정렬할 배열 숫자 개수

int maxValue=0;     //가장 큰 값

clock_t start, finish, used_time=0;    //실행 시간 측정을 위한 변수

//배열의 item을 random으로 삽입
void input_random(int *array,int arrlen){
    srand((unsigned int)time(NULL));
    for(int i=0;i<arrlen;i++){
        array[i]=rand()%MAX_ARRAY_SIZE + 1;
        maxValue = (maxValue < array[i]) ? array[i] : maxValue;     //가장 큰 값 찾기
    }
}

void display(int *array,int arrlen){
    for(int i=0; i<arrlen;i++){
        cout.width(3);
        cout << i+1 <<". ";
        cout.width(2);
        cout << array[i] << endl;
    }
}

//실행 시간을 측정 및 출력하는 함수
void CalcTime(void){
    used_time=finish-start;
    printf("\n*********** result **********\n     time : %lf sec\n", (double)(used_time)/CLOCKS_PER_SEC);
}

int* count_sort(int *array,int arrlen){
    int *c = new int[maxValue+1];
    int c_size = maxValue+1;
    int *b = new int[arrlen];

    memset(c,0,c_size*sizeof(int)); //배열c 0으로 초기화

    /*각 숫자 횟수 카운틱하며 c배열 인덱스 값 증가*/
    for(int i = 0; i < arrlen ; i++){
        c[array[i]]++;
    }

    /*배열 c의 인덱스 값 누적합 구하기*/
    for(int i=1; i < c_size; i++){
        c[i] += c[i-1];
    }

    /*배열 A역순으로 훑으며, 배열 C참조하여 정렬*/
    for(int i = arrlen-1; i >= 0; i-- ){
        b[c[array[i]]-1] = array[i];
        --c[array[i]];
    }
    return b;
}

int main(){
    int *array = new int[MAX_ARRAY_SIZE];

    input_random(array,MAX_ARRAY_SIZE);     //배열에 랜덤값 삽입
    delete array;

    start = clock();
    array = count_sort(array,MAX_ARRAY_SIZE);       //계수 정렬
    finish = clock();
    display(array,MAX_ARRAY_SIZE);          //show array
    CalcTime();

    delete array;
    return 0;
}

4. 특징

시간 복잡도는 O(n+k)O(n) 또는 O(k) 를 갖는다.
( 여기서 n은 input의 개수, k는 input으로 들어온 n의 범위 == 추가 배열 c의 범위 )

Note

예를 들어 1,1,1,1,1,1,1,1 과같이 1이 10개로 n이 10이 들어왔다면, 이때 k는 2가 되고,
1 500 의 input이 주어진다면 n은 2에 불과하지만, k는 501이 된다.

범위가 넓어지게 되면 추가 메모리 공간이 많이 필요해지고, 또 범위를 계산해서 가장 큰 수가 무엇인지 판별하는 추가 로직도 도는 등 추가 작업이 필요하다.


따라서, 점수알파벳같은 좁은 범위의 데이터를 정렬할 때 유용하다.

stable 한 정렬 방법으로 radix sort 시 사용되는 정렬이다.

Related Posts

블로그 첫 시작 👋

블로그 첫 시작 👋

우선, 나는 말수가 적은 편이다. 말을 적게한다고 생각을 안하는 것이 아니라 머리속에서 생각은 정말 많으나, 그것을 입밖으로 꺼낼때 정리가 되지 않아 버벅거리기도 하고, 조사를 잘못 선택하여 말을 하거나 말을 하는 도중에 머리속이 꼬여 중간에 말을 멈추기도 한다. 말은 생각을 언어로 바꾼 것이기 때문에 많은 생각들을 정리하는 연습과 맞춤법, 올바른 조사사용 등 올바른 언어 습관을 글쓰기를 통해 얻고자 글쓰기를 시작하기로 했다....

Read More
상수

상수

상수는 Immutable(불변)한 특징을 갖으며 한 번 할당된 값을 변경할 수 없는 변수이다. 상수 키워드는 const로 선언방식은 변수 var와 같고 short assginment statement는 var키워드 전용이기 때문에 상수는 짧은 대입문 사용이 불가능 하다. 또한, 반드시 컴파일 타임에 실행이 가능한 표현식이어야만 한다....

Read More
[SPSP] Bellman Ford 알고리즘

[SPSP] Bellman Ford 알고리즘

그래프 중에서 최단 경로를 찾는 알고리즘중에 하나로 하나의 정점에서 다른 모든 정점까지의 최단경로를 구하는 알고리즘 (single-source shortest path algorithmm)으로 음의 가중치도 계산 할수 있는 알고리즘이다. Vertex의 개수가 N개일 때, 한 vertex에서 다른 vertex까지 가는데 거치는 edge수는 최소 1개부터 최대 N-1번 거치게 된다. 이때, relax의 개념을 이용하며 relax는 현재 계산된 v노드까지의 거리보다 현재 노드 u까지의 경로와 u에서 v의 가중치 (e(u,v)) 가 더 작다면 값을 갱신해주는 것이다....

Read More