Notice
Recent Posts
Recent Comments
초코레
퀵 정렬(quick sort) 본문
- 가장 빠른 정렬 알고리즘 중 하나
- 피벗을 기준으로 피벗보다 작은 요소로 이루어진 부분과 피벗보다 큰 요소 이루어진 부분으로 나눔을 반복함으로써 최종적으로 정렬 된다.
- 즉 각 그룹에 대해 피벗 설정과 그룹 나눔을 반복하여 모든 그룹의 개수가 1이 되면 정렬을 마친다.
- 서로 이웃하지 않고 멀리 떨어져 있는 요소를 교환해야 하므로 안정적이지 않은 알고리즘이다.
- 시간 복잡도는 O(n log n)
- 정렬할 배열의 초깃값이나 피벗의 선택 방법에 따라 시간 복잡도가 증가하는 경우도 있다.
- 최악의 시간 복잡도는 O(n^2)이 된다.
배열을 두 그룹으로 나누기
- 피벗 이하의 요소를 배열 왼쪽으로 옮긴다.
- a[pl] >= x 가 성립할 때까지 pl을 오른쪽으로 스캔
- 피벗 이상의 요소를 배열 오른쪽으로 옮긴다.
- a[pr] <= x 가 성립할 때까지 pr을 왼쪽으로 스캔
- pl과 pr이 교차하면 그룹을 나누는 과정이 끝나고 배열은 두 그룹으로 나누어진다.
- 피벗 이하의 그룹 : a[0] ~ a[pl - 1]
- 피벗 이상의 그룹 : a[pr + 1] ~ a[n - 1]
- pl > pr + 1 인 경우에는 피벗과 일치하는 값을 가진 그룹이 생길 수 있다 : a[pr + 1] ~ a[pl - 1]
퀵 정렬은 분할 정복 알고리즘이므로 재귀 호출을 사용하여 구현할 수 있다.
- 요소의 개수가 2개 이상인 각 그룹을 나누는 작업을 반복하기 위해 재귀 호출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//퀵 정렬
static void quickSort(int[] a, int left, int right) {
int pl = left; //왼쪽 커서
int pr = right; //오른쪽 커서
int x = a[(pl + pr) / 2]; //피벗
//배열 a를 피벗 x를 기준으로 나눈다
do {
while (a[pl] < x) pl++;
while (a[pr] > x) pr--;
if (pl <= pr)
swap(a, pl++, pr--);
} while (pl <= pr);
//요소의 개수가 2개 이상인 그룹만 나누기 위한 조건
if (left < pr) quickSort(a, left, pr); //피벗 이하인 부분
if (pl < right) quickSort(a, pl, right); //피벗 이상인 부분
}
|
cs |
비재귀적인 퀵 정렬
- 사용하여 비재귀적인 퀵 정렬을 구현할 수 있다.
- 스택에 푸시한 값은 나눌 배열의 첫 요소와 끝 요소의 인덱스이다.
- 배열을 나누는 작업이 끝나면 하위 그룹 인덱스와 상위 그룹 인덱스를 푸시한다.
- 스택에서 팝한 범위를 나누는 작업을 반복하여 정렬을 수행한다.
- 스택이 비면 정렬이 끝난다.
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
|
// 퀵정렬(비재귀버전)
static void quickSort(int[] a, int left, int right) {
IntStack lstack = new IntStack(right - left + 1); //나눌 범위의 왼쪽 끝 요소의 인덱스를 저장하는 스택
IntStack rstack = new IntStack(right - left + 1); //나눌 범위의 오른쪽 끝 요소의 인덱스를 저장하는 스택
lstack.push(left);
rstack.push(right);
while (lstack.isEmpty() != true) {
//정렬할 배열의 범위
int pl = left = lstack.pop(); //왼쪽 커서
int pr = right = rstack.pop(); //오른쪽 커서
int x = a[(left + right) / 2]; //피벗
//피벗을 기준으로 하위 그룹과 상위 그룹으로 나눈다
do {
while (a[pl] < x) pl++;
while (a[pr] > x) pr--;
if (pl <= pr)
swap(a, pl++, pr--);
} while (pl <= pr);
if (left < pr) {
lstack.push(left); //하위 그룹 범위의
rstack.push(pr); //인덱스를 푸시
}
if (pl < right) {
lstack.push(pl); //상위 그룹 범위의
rstack.push(right); //인덱스를 푸시
}
}
}
|
cs |
분할한 그룹 중 요소의 개수가 적은 그룹을 먼저 나누기
- 배열을 나눈 뒤에 얻는 하위 그룹과 상위 그룹의 요소 개수를 비교하여 많고 적음에 따라 푸시 순서를 바꾸면 스택에 최대로 쌓이는 데이터 수를 조절할 수 있다.
- 요소의 개수가 많은 그룹을 먼저 푸시하면 요소의 개수가 적은 그룹을 먼저 나누게 된다.
- 즉 요소의 개수가 적은 배열일수록 적은 횟수로 분할을 종료할 수 있게 되고 스택에 쌓이는 데이터의 최대 개수가 적어진다.
- 만약 배열의 길이가 n이라고 한다면 스택에 쌓이는 데이터의 최대 개수는 log n보다 적게 된다.
피벗 선택하기
- 피벗을 선택하는 방법에 따라 퀵 정렬의 실행 효율에 큰 영향을 미친다.
- [방법 1] : 나눌 배열의 요소 개수가 3 이상이면 임의로 요소 3개를 선택하고 그 중에서 중간 값인 요소를 피벗으로 선택한다.
- [방법 2] : 나눌 배열의 처음, 가운데, 끝 요소를 정렬한 다음 가운데 요소와 끝에서 두 번째 요소를 교환한다. 피벗으로 끝에서 두 번째 요소의 값(a[right - 1])을 선택하여 나눌 대상의 범위를 a[left + 1] ~ a[right - 2]로 좁힌다.
'알고리즘' 카테고리의 다른 글
힙 정렬(heap sort) (0) | 2019.12.29 |
---|---|
병합 정렬(merge sort) (0) | 2019.12.29 |
셸 정렬(shell sort) (0) | 2019.12.28 |
단순 삽입 정렬(straight insertion sort) / 셔틀 정렬(shuttle sort) (0) | 2019.12.28 |
단순 선택 정렬(straight selection sort) (0) | 2019.12.27 |