10989. 수 정렬하기 3
문제
입력
첫째 줄에 수의 개수 N(1 ≤ N ≤ 10,000,000)이 주어진다. 둘째 줄부터 N개의 줄에는 숫자가 주어진다. 이 수는 10,000보다 작거나 같은 자연수이다.
출력
첫째 줄부터 N개의 줄에 오름차순으로 정렬한 결과를 한 줄에 하나씩 출력한다.
예제 입력
10
5
2
3
1
4
2
3
5
1
7
예제 출력
1
1
2
2
3
3
4
5
5
7
1. 카운팅 정렬 소개
이제 소개할 카운팅 정렬은 매애우 간단하고 빠른 알고리즘이다. 간단하고 빠른 만큼 제한적이고, 메모리를 많이 소비한다.
쉬운 버블 / 삽입 정렬은 O(n ^ 2) 이었고, 병합 / 힙 정렬은 O(n * log n) 이었다.
카운팅 정렬은 O(n) 으로 선형시간이다!
카운팅 정렬은 말 그대로 숫자가 몇 번이나 나오는지 세는 알고리즘이다. 소개하기도 뭐한 매우 간단한 알고리즘인데,
필요한 배열은 숫자가 몇 번 나오는지 기록할 배열이다. 위의 문제에선 등장할 수가 10,000 보다 작거나 같은 자연수로 제한되어 있기 때문에 만칸의 배열만 준비하면 된다.
따라서 카운팅 정렬은 정렬할 원소들의 범위를 알고 있는 상황에서만 쓸 수 있다.
또한 그 범위는 메모리에 올릴 수 있을 정도여야한다.
[1, 100, 2, 1, 50] 이라면 백 칸짜리 배열이 필요하겠다.
이제 정렬한 결과를 출력하는데는 두 가지 방법이 있다.
첫째로는 걍 백 칸짜리 배열을 앞에서 부터 보면서 기록된 갯수만큼 출력하는 것이다.
인덱스 1을 보고 기록된 숫자가 2 이므로 1을 두 번 출력한다.
이 경우에 메모리 사용량은 숫자 등장 범위에 영향을 받는다.
두번째 방법으로는 백 칸짜리 기록용 배열을 누적합으로 바꿔주는 것이다.
-> 누적합[1] = 2 / 누적합[2] = 3 / ... / 누적합[50] = 4 / ... / 누적합[100] = 5
이제 기록용 배열은 해당 숫자가 정렬된 배열의 어떤 범위에 존재해야 하는지 나타낸다.
즉, 누적합[1] = 2 이므로, 배열의 앞에서 부터 두칸을 차지한다는 뜻이고,
바로 뒤의 누적합[2]는 3이니까 세번째 칸을 차지한다는 뜻이다.
누적합[3] [4] ... [49] 는 연속해서 3이므로 정렬결과용 배열에 들어가지 않는다.
또한 이 방법은 입력순서를 기억해야한다. 마지막 입력은 50 이었고 누적합[50] = 4 였으므로,
50은 정렬용 배열 네번째 칸에 들어간다. 누적합[50] = 3으로 줄어든다.
50의 전 입력은 1이었다. 누적합[1] = 2 이므로 1은 정렬용 배열 두번째 칸에 들어간다. 누적합[1] = 1로 줄어든다.
이 방법은 따라서 입력의 갯수에 비례해서 연산을 하고, 메모리도 차지한다.
두 방법중에 알맞은 방법을 쓰기로 하자.
위의 문제에선 숫자 등장범위가 10,000 이고 입력은 10,000,000 개 이니까 첫번째 방법을 쓰기로 했다.
2. 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <stdio.h> using namespace std; int n, tmp; int count[10001]; int main() { scanf("%d", &n); for (int i = 0; i < n; ++i) { scanf("%d", &tmp); count[tmp]++; } for (int i = 1; i <= 10000; ++i) while (count[i]--) printf("%d\n", i); return 0; } | cs |
3. 시간 복잡도
일단 입력이 n개 들어오므로 입력엔 O(n)이 걸린다. 출력도 결국엔 n개를 출력해야 하므로 O(n)이다.
'알고리즘 > 정렬 알고리즘' 카테고리의 다른 글
4. 힙 정렬 heap sort (0) | 2017.09.27 |
---|---|
3. 병합 정렬 merge sort (0) | 2017.09.25 |
2. 거품 정렬 bubble sort (0) | 2017.09.25 |
1. 삽입 정렬 insertion sort (0) | 2017.09.25 |