카테고리 없음
Randomized Quicksort
파이도
2019. 4. 19. 18:13
Problem Solving (PS)를 할 때 정렬은 직접 구현하지 않고 std::sort를 이용하는 것이 상식이다.
왜냐면, 직접 구현하는 것이 까다롭기 때문이다. 퀵소트를 구현할 경우, 까딱하면 O(N^2)가 되어 시간초과를 내기 십상이다.
하지만 Randomization을 구현하고 적절한 tie-breaking을 구현하면 아래와 같이 평균 시간복잡도 O(N lg N)을 낼 수 있다.
코드 설명
- 기본적인 Quicksort 구현에 Pivot 선택 시 Randomization을 추가한 형태다.
- 랜덤한 숫자를 하나 골라 srand()를 호출해주면 기본 시드(seed)값에 대해 시간 초과가 나도록 설계된 데이터에서 시간 초과를 피할 수 있다.
- Randomization을 하더라도 구간 내의 숫자가 모두 같은 경우 맨 끝의 원소가 Pivot으로 선택되는 문제가 있다. 따라서 이를 막기 위해 Pivot값과 일치하는 숫자는 1/2의 확률로 왼쪽, 1/2의 확률로 오른쪽으로 분류한다. (CLRS Exercise 7.1-2 참조)
이렇게 하면 Online Judge (e.g. Baekjoon) 에서도 거의 평균 시간복잡도로 돌아간다.
https://www.acmicpc.net/problem/2751 에서 확인해보자.
Note 엄밀하게 말하면 아래의 rand_range 함수는 구간 길이가 RAND_MAX보다 클 때 특별한 처리를 해 주어야 한다. 하지만 굳이 그렇게 하지 않아도 Randomization에 의해 그냥 적당히 돌아가는 것 같다. 어차피 몇 번만 분할하면 구간 길이는 금방 RAND_MAX보다 작아질 테니까. (* RAND_MAX는 보통 32767이다.)
Sample Code
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#include <set>
#include <stack>
#include <functional>
#include <queue>
#include <math.h>
#include <memory.h>
int readInt();
int N;
int P[1000000];
void GatherInput() {
N = readInt();
for (int i = 0; i < N; i++) {
P[i] = readInt();
}
}
int rand_range(int L, int R) {
double tmp = rand() / ((double)RAND_MAX + 1); // tmp belongs to [0, 1)
return (int)(tmp * (R - L + 1) + L);
}
int partition(int L, int R) {
// pick pivot randomly
int pivot = rand_range(L, R);
std::swap(P[pivot], P[R]);
// the same for non-randomized version
int pivot_value = P[R];
// loop invariant: [L, i) contains left elements
// and [i, j) contains right elements
int i, j;
for (i = L, j = L; j < R; j++) {
if (P[j] < pivot_value || (j % 2 == 0 && P[j] == pivot_value)) {
// left
// exchange it with P[i]
std::swap(P[i], P[j]);
i++;
}
else {
// right
// just keep it
}
}
std::swap(P[i], P[j]);
return i;
}
void quicksort(int L, int R) {
if (L < R) {
int pivot = partition(L, R);
quicksort(L, pivot - 1);
quicksort(pivot + 1, R);
}
}
void Solve() {
srand(7598034);
quicksort(0, N - 1);
for (int i = 0; i < N; i++) {
printf("%d\n", P[i]);
}
}
int main() {
#ifdef BOJ
int T = 1;
#else
setbuf(stdout, NULL);
int T = readInt();
#endif
for (int test_case = 1; test_case <= T; test_case++) {
GatherInput();
#ifndef BOJ
printf("Case #%d\n", test_case);
#endif
Solve();
}
return 0;
}
int readInt() {
int p;
scanf("%d", &p);
return p;
}
|
s |