[Algorithm] Counting Sort
Counting Sort
Counting Sort
숫자로 된 배열을 오름차순으로 정렬하는 알고리즘입니다.
다만 숫자는 정수여야만 적용 가능합니다.
이 알고리즘의 또 다른 특징은 대부분의 정렬 알고리즘과는 달리
비교환 알고리즘이라는 것입니다.
예를 들어 bubble sort는 두 숫자를 크기비교 후, 교환하는 방식이였죠.
{0, 4, 1, 3, 1, 2, 4, 1}을 Counting sort를 통해 정렬해보겠습니다.
위의 배열의 이름을 data라고 하겠습니다.
먼저 배열에 들어있는 숫자 중에서 최대값을 찾고 k라 하겠습니다.
여기서는 k == 4 가 되겠죠.
사실 k는 최대값보다 큰 숫자 아무거나 사용해도 정렬이 되긴하지만,
최대값에 가깝게 설정할수록 적은 연산으로 정렬됩니다.
(k+1)크기의 배열 temp를 설정합니다. 여기서는 크기가 5인 배열입니다.
temp의 n번째 자리에는 data배열에서 숫자 n의 갯수가 들어갑니다.
여기서는
temp[0] = 1, temp[1] = 3, temp[2] = 1, temp[3] = 1, temp[4] = 2
즉 temp = {1, 3, 1, 1, 2} 가 됩니다.
왜 가장 큰 숫자를 구하고 그보다 1 큰 크기의 배열을 만들어야 하는지 아시겠죠.
이제 temp 배열을 조금 가공해줘야 합니다.
n == 1 부터 시작해서 4까지
temp[n] = temp[n] + temp[n-1], where \(0 \lt n \le k\)
같은 방식으로 바꿔줍니다.
여기서는
temp[1] = temp[1] + temp[0], 즉 temp[1] == 4
temp[2] = temp[2] + temp[1], 즉 temp[2] == 5
…
이런식으로 가공하면 temp배열은 {1, 4, 5, 6, 8}이 됩니다.
이렇게 가공을 한 목적을 알아야겠죠.
예를 들어서 temp[1]을 뜻하는 바를 알아보겠습니다.
data배열은 숫자가 8개밖에 안되니까 우리가 그냥 눈으로도 정렬할 수 있긴합니다.
그렇게 오름차순으로 정렬하면 {0, 1, 1, 1, 2, 3, 4, 4}가 됩니다.
숫자 1이 끝나는 지점이 3번째이죠.(0번째 부터 시작하므로)
temp[1] == 4 가 의미하는 바는 1이란 숫자는 (4-1)번째 자리에서 끝난다
를 의미합니다.
즉, 우리는 {1, 4, 5, 6, 8}만 보고도 data배열을 정렬할 수 있는 것입니다.
data배열을 오름차순으로 정렬한 배열을 sorted라 하고 현재 다 비어있는 상태라고 가정하겠습니다.
즉, sorted = {null, null, null, null, null, null, null, null}입니다.
temp[0] == 1 이고
숫자 0은 (1-1)번째 자리에서 끝나므로, sorted = {0, null, null, null, null, null, null, null}
숫자 1은 숫자 0다음부터 시작하고, (4-1)번째 자리에서 끝나므로, sorted = {0, 1, 1, 1, null, null, null, null}
숫자 2는 숫자 1다음부터 시작하고, (5-1)번째 자리에서 끝나므로, sorted = {0, 1, 1, 1, 2, null, null, null}
숫자 3은 숫자 2다음부터 시작하고, (6-1)번째 자리에서 끝나므로, sorted = {0, 1, 1, 1, 2, 3, null, null}
숫자 4는 숫자 3다음부터 시작하고, (8-1)번째 자리에서 끝나므로, sorted = {0, 1, 1, 1, 2, 3, 4, 4}
이렇게 정렬이 끝났습니다.
이 과정을 코드로 표현할 때는 다음과 같이 진행합니다.
temp가 {1, 4, 5, 6, 8} 인걸 구한 다음,
data배열의 뒤 숫자부터 계산을 시작합니다.
data배열의 맨 뒤 숫자는 1이고 temp[1]은 4입니다.
그럼 temp[1]을 3으로 조정한 후(1을 뺀 후), sorted[3]에 1을 대입합니다.
이 과정을 배열의 모든 요소에 대해서 진행하면 됩니다.
복잡도(Time Complexity)
data 배열의 크기는 n이라 하고,
temp 배열의 크기를 k라 하겠습니다.
일단 처음에 data 배열을 돌면서 각 숫자의 갯수를 세야하므로 n번 돌아야합니다.
그 다음, temp 배열을 돌면서 각 숫자의 갯수값을 넣어야하므로 k번 돌아야합니다.
즉, \(O(n+k)\) 가 되는 것이죠.
그래서 k의 값을 최대값에 최대한 가깝게 설정하는게 좋다고 한 것입니다.
bubble sort의 복잡도는 \(O(n^2)\) 이므로, counting sort가 더 좋아보일 수도 있지만,
\(n+k \gt n^2\) 인 경우에는 차라리 bubble sort로 정렬하는게 더 낫습니다.
극단적인 예를 들어 n == 10인데 k == 10000이면
버블소트 복잡도는 O(100)인데 카운팅소트 복잡도는 O(10010)이 되므로
k값을 다시 설정할 수 있으면 다시 설정하고, 아니면
다른 정렬 알고리즘을 쓰는게 나을 것입니다.
예제 코드
CountingSort.java
import java.util.Random;
public class CountingSort {
public static void main(String[] ar){
CountingSort ex = new CountingSort();
int arraySize = 8;
Random rand = new Random();
int[] intArray = new int[arraySize];
//배열에 값 설정
for(int loop=0; loop<arraySize; loop++){
intArray[loop] = rand.nextInt(10)+1;
}
System.out.print("New array => ");
ex.printArray(intArray);
System.out.println();
System.out.println("sorting process START!!!");
ex.doCountingSort(intArray);
}
public void doCountingSort(int[] inputArray){
int inputArrayLength = inputArray.length;
int[] counts = new int[11];
int[] sortedArray = new int[inputArrayLength];
// count 배열 생성
for(int item: inputArray){
counts[item]++;
}
System.out.print("counts => ");
printArray(counts);
// count 배열 작업
for(int loop=1; loop<11; loop++){
counts[loop] += counts[loop-1];
}
System.out.print("processed counts => ");
printArray(counts);
System.out.println("==========================");
for(int loop=inputArrayLength-1; loop>=0; loop--){
System.out.println((inputArrayLength-loop) + "th sorting process");
sortedArray[counts[inputArray[loop]]-1] = inputArray[loop];
counts[inputArray[loop]] -= 1;
System.out.print("sortedArray => ");
printArray(sortedArray);
System.out.print("counts => ");
printArray(counts);
System.out.println();
}
}
public void printArray(int[] inputArray){
for(int item: inputArray){
System.out.print(item + " ");
}
System.out.println();
}
}
//New array => 2 5 3 3 5 5 8 1
//
//sorting process START!!!
//counts => 0 1 1 2 0 3 0 0 1 0 0
//processed counts => 0 1 2 4 4 7 7 7 8 8 8
//==========================
//1th sorting process
//sortedArray => 1 0 0 0 0 0 0 0
//counts => 0 0 2 4 4 7 7 7 8 8 8
//
//2th sorting process
//sortedArray => 1 0 0 0 0 0 0 8
//counts => 0 0 2 4 4 7 7 7 7 8 8
//
//3th sorting process
//sortedArray => 1 0 0 0 0 0 5 8
//counts => 0 0 2 4 4 6 7 7 7 8 8
//
//4th sorting process
//sortedArray => 1 0 0 0 0 5 5 8
//counts => 0 0 2 4 4 5 7 7 7 8 8
//
//5th sorting process
//sortedArray => 1 0 0 3 0 5 5 8
//counts => 0 0 2 3 4 5 7 7 7 8 8
//
//6th sorting process
//sortedArray => 1 0 3 3 0 5 5 8
//counts => 0 0 2 2 4 5 7 7 7 8 8
//
//7th sorting process
//sortedArray => 1 0 3 3 5 5 5 8
//counts => 0 0 2 2 4 4 7 7 7 8 8
//
//8th sorting process
//sortedArray => 1 2 3 3 5 5 5 8
//counts => 0 0 1 2 4 4 7 7 7 8 8