알고리즘 학습 페이지

카운팅 정렬(Counting Sort)

Xerath(제라스) 2021. 11. 29. 21:37
728x90
반응형

[정렬 방법]

정렬에 앞서, 이 정렬을 이용하고자 할 때 조건은 정해진 범위 내의 숫자들만을 배열의 원소값으로 가져야 한다는 것이다. 이렇기에 나중에 타 정렬들과 비교했을 때 상대적으로 시간 복잡도가 작고 빠른 특성을 띤다.

먼저, 임의의 배열 arr에 정해진 범위의 숫자들(ex)0~20)을 넣는다. 이후 20 크기의 배열 c_arr을 만들고 arr을 for문을 돌리면서 원소값과 같은 index의 c_arr의 값을 1씩 증가시킨다. 이후 결과로 사용할 result 배열에 arr의 값들을 정렬시킬 것인데, arr의 가장 마지막 원소값(k)부터 시작해서 그 원소값과 같은 인덱스의 c_arr의 원소값과 같은 인덱스의 result 배열의 원소값에 k를 넣는다. 이 과정을 거치면 사용된 c_arr의 원소값을 -1하고 arr의 값 또한 그 왼쪽의 값으로 이동하여 같은 과정을 거친다.


[정렬 과정]

n 크기의 임의의 배열 arr에 정해진 범위의 숫자들(ex)0~k)을 입력한다.

 

이후, 정해진 범위의 숫자 크기(0~k이므로 k+1)의 배열 c_arr을 만드는데 이 배열은 arr에 있는 값들의 개수를 저장하는 배열이다.

 

for문을 통해 arr[0]~arr[n-1]까지의 값들을 확인하며 그 값과 같은 인덱스의 c_arr 원소값을 +1한다.

이후, c_arr을 누적형태로 바꾸어나간다.

ex) 0, 1, 1, 3, 2  -> 0, 1, 2, 5, 7

코드로 표현하자면, for i <- 0 to k-1 { c_arr[i+1] += c_arr[i];

이후, 정렬된 arr의 값들을 적어둘 arr과 같은 크기의 배열 result를 만든다.

 

<1단계>

arr[n-1]과 같은 인덱스의 c_arr의 원소값과 같은 인덱스의 result의 원소에 arr[n-1] 값을 입력한 후, c_arr의 원소값은 1 감소시킨다.

코드로 표현하자면,

result[ c_arr[ arr[n-1] ]-1 ] = arr[n-1];

c_arr[ arr[n-1] ]--;

 

<2단계>

 

arr[n-2]과 같은 인덱스의 c_arr의 원소값과 같은 인덱스의 result의 원소에 arr[n-2] 값을 입력한다.

코드로 표현하자면, result[ c_arr[ arr[n-2] ]-1 ] = arr[n-2];

c_arr[ arr[n-2] ]--;

 

.

.

.

 

<n단계>

 

arr[0]과 같은 인덱스의 c_arr의 원소값과 같은 인덱스의 result의 원소에 arr[0] 값을 입력한다.

코드로 표현하자면, result[ c_arr[ arr[0] ]-1 ] = arr[0];

c_arr[ arr[0] ]--;

 

이 과정이 모두 끝나면 arr에 중복되어 들어가 있는 숫자들이 result에 정렬이 된다.

 


[pseudo code]

CountingSort(arr, result, k) //정렬할 배열 arr과 정렬될 공간인 result 배열, 정해진 범위의 숫자
for i <- 0 to k
  do c_arr[i] <- 0 //c_arr배열의 element들을 모두 0으로 초기화시킨다.
for j <- 0 to length[arr]-1
  do c_arr[arr[j]] <- c_arr[arr[j]] + 1 //arr에 있는 원소값과 같은 인덱스의 c_arr 원소값들을 1씩 증가시킨다. 
for i <- 0 to k-1
  do c_arr[i] <- c_arr[i] + c_arr[i-1] //이후 c_arr의 값들을 작은 인덱스의 원소값들부터 누적한 값으로 변경한다.
for j <- length[c_arr]-1 downto 0
  do result[c_arr[arr[j]]-1] <- arr[j] //arr배열의 끝부터 왼쪽방향으로 해서 result값에 정렬시켜나간다.
    c_arr[arr[j]] <- c_arr[arr[j]] - 1 //값을 집어넣은 후에는 해당 인덱스의 c_arr 원소값을 1 감소한다.

[코드 구현]

 

public class CountingSort {
  
    public static void main(String[] args) {
        int[] arr = {22, 5, 34, 1, 32, 18, 50, 8}; //예시로 작성한 배열
        StringBuilder sb = new StringBuilder();
        int [] result = new int [arr.length];
      
      countingSort(arr, result, 50);
      for (int a : result) {
        sb.append(a);
        sb.append(" ");
      }
      System.out.println(sb);
    }
  
    private static void countingSort(int[] arr, int[] result, int k) {
        int[] c_arr = new int[k+1];
        for(int i=0; i <=arr.length-1; i++){
            c_arr[arr[i]]++;
        }
        
        for(int i=0; i <k; i++){
            c_arr[i+1] += c_arr[i];
        }

        for(int i=arr.length-1; i>=0; i--){
            result[c_arr[arr[i]]-1] = arr[i];
            c_arr[arr[i]]--;
        }
    }
}

 


[시간 복잡도]

countingSort로 들어오기 전 arr배열을 입력받으면 그 길이가 n이므로 T(n)이고, 

countingSort 함수 안에서 T(n) + T(k) + T(n)이므로 시간 복잡도는 O(n+k)이다.

이때, k = O(n)이라면 시간복잡도는 O(n)이지만, 터무니없이 커지거나 더 크다면 시간복잡도는 더 커진다.

그래서 k가 클 경우에는 실용적이지 못하다.

 

또한, 카운팅 정렬은 입력받은 숫자들 중 동일한 값들이 있을 때 FIFO구조 성질을 만족하므로 stable 정렬 알고리즘이다.

728x90
반응형