퀵 정렬(Quick Sort)
알고리즘이 매우 빠르다라고 해서 붙여진 이름이다.
기준이 되는 피벗(pivot)을 설정하고, 그룹을 반복적으로 나누어서 정렬을 하는 알고리즘이다.
퀵 정렬은 피벗을 배열의 왼쪽 끝으로 설정하냐, 오른쪽 끝으로 설정하냐, 가운데로 설정하냐에 따라서 속도가 달라진다.
퀵 정렬의 알고리즘을 간단히 설명하면 이렇다.
배열의 왼쪽 끝 요소를 가리키는 커서를 pl, 오른쪽 끝 요소를 가리키는 커서를 pr 로 한다.
그리고 피벗을 가운데 값인 8로 한다고 하자.
피벗을 기준으로 왼쪽에 있는 구역을 A 구역, 오른쪽에 있는 구역을 B 구역이라고 할 때,
A 구역에서는 pl을 오른쪽으로 이동시키면서 8보다 큰 값들을 찾아서 B 구역으로 넘기고,
B 구역에서는 pr을 왼쪽으로 이동시키면서 8보다 작은 값들을 찾아서 A 구역으로 넘기는 것이다.
그렇게 되면 결과적으로 A 구역에는 8보다 작은 값들이, B 구역에는 8보다 큰 값들이 위치하게 된다.
그리고 나서 아래의 배열을 두 그룹으로 나눈 후, 위 과정을 똑같이 반복하면 된다.
시간 복잡도 : O(NlogN)
최악의 시간 복잡도 : O(N²)
퀵 정렬은 배열의 초기상태나 피벗의 위치에 따라서 최악의 경우 시간 복잡도가 O(N²) 가 될 수 있다.
알고리즘 :
1. 피벗을 설정한다.
2. 배열의 시작 인덱스를 left, 마지막 인덱스를 right라고 할 때,
- pl은 left부터 시작하여 피벗보다 크거나 같은 값이 있을 때까지 오른쪽으로 이동한다.
(단, pl이 right보다 작거나 같을때까지만 이동)
- pr은 마지막부터 시작하여 피벗보다 작거나 같은 값이 있을 때까지 왼쪽으로 이동한다.
(단, pr이 left보다 크거나 같을때까지만 이동)
3. pl과 pr 이 이동을 멈추면, pl<=pr 이면 arr[pl] 과 arr[pr] 을 서로 교환(swap)한다.
교환이 끝나면 pl은 오른쪽으로 하나 이동, pr은 왼쪽으로 하나 이동한다.
4. pl과 pr이 교차할 때까지, 즉, pl > pr 일 때까지 2,3번을 반복한다.
5. pl 과 pr이 교차하면,
시작 인덱스를 left, 끝 인덱스를 pr로 하는 그룹으로 쪼개어서 1,2,3번 과정을 반복한다.
(단, left < pr 조건을 만족해야 함)
마찬가지로, 시작 인덱스를 pl, 끝 인덱스를 right로 하는 그룹으로 쪼깨어서 1,2,3번 과정을 반복한다.
(단, pl < right 조건을 만족해야 함)
6. 더 이상 작은 그룹으로 쪼갤 수 없으면 종료한다.
[예제]
배열 arr의 요소가 차례대로 5 9 3 1 8 4 6 2 7 일 때, 이 배열을 오름차순으로 정렬하기
이번 예제에서는 피벗을 가운데값으로 설정할 것이다.
배열의 왼쪽 끝 인덱스를 left = 0, 오른쪽 끝 인덱스를 right = 8 이라고 하면,
처음 초기값은 pl = left, pr = right, pivot = 8 이다.
pl에 위치한 값이 8보다 클 때까지 pl이 오른쪽으로 이동하면, pl은 9가 있는 1번 인덱스에서 멈춘다.
pr에 위치한 값이 8보다 작을 때까지 pr이 왼쪽으로 이동하면, pr은 7이 있는 8번 인덱스에서 멈춘다.
arr[1] 과 arr[8] 을 서로 교환한다.
교환이 끝나면 pl은 하나 증가하여 3을 가리키고, pr은 하나 감소하여 2를 가리킨다.
pl이 8보다 크거나 같을 때까지 이동하면 pl = 4 에서 이동을 멈춘다.
pr의 경우, arr[7] = 2 이므로 8보다 작으므로 이동을 하지 않는다. pr = 7
pl과 pr의 이동이 끝나면 arr[4] 와 arr[7]를 교환한다.
교환이 일어난 후, pl은 오른쪽으로 한칸 이동하여 pl = 5가 되고, pr은 왼쪽으로 한칸 이동하여 pr = 6가 된다.
pl 이 8보다 크거나 같은 값이 나올 때까지 이동하면 pl = 7 에서 이동을 멈춘다.
pr은 arr[6] = 6이 8보다 작으므로 pr = 6 에서 이동을 하지 않는다.
pr < pl 이 되었으므로 pl과 pr이 교차가 일어나 반복문이 종료된다.
left = 0을 시작으로하고 pr = 6을 끝으로 하는 그룹과, pl = 7을 시작으로 하고 right = 8을 끝으로 하는 그룹으로 나뉘어진다.
먼저 왼쪽 그룹부터 보자.
초기 상태는 pl = 0, pr = 6, pivot = 1 이다.
arr[0] = 5로 1보다 크므로 pl은 pl = 0 에서 더 이상 움직이지 않는다.
pr은 1보다 작거나 같은 수를 찾을 때까지 왼쪽으로 이동한다. pr = 3 일 때 이동을 멈춘다.
arr[0] 과 arr[3]를 교환한다.
교환이 일어난 후, pl은 오른쪽으로 하나 증가하여 pl = 1, pr은 왼쪽으로 하나 감소하여 pr = 2가 된다.
pl은 arr[1] = 7 이므로 피벗보다 크기 때문에 이동을 하지 않고 멈춘다.
pr은 피벗보다 작거나 같을 때까지 왼쪽으로 이동하면 pr = 0 일 때 이동을 멈추게 된다.
pr < pl 이 되었으므로 pl과 pr이 교차가 일어나 반복문이 종료된다.
left < pr이 성립하지 않으므로 왼쪽 그룹은 만들어지지 않는다.
pl = 1을 시작으로 하고 right = 6을 끝으로 하는 그룹이 생긴다.
이제 다시 돌아가서 오른쪽 그룹(8, 9)을 보자.
pl은 arr[7] = 8 이므로 피벗과 크거나 같다는 조건을 만족하므로 pl = 7에서 이동을 하지 않는다.
pr은 8보다 작거나 같을 때까지 이동하면, pr = 7에서 이동을 멈춘다.
arr[7] 와 arr[7]을 교환한다. (같은 위치의 값끼리 교환하므로 결과는 동일)
교환 후에 pr은 오른쪽으로 하나 이동하지만, pl은 left<=pl 조건을 만족해야 하므로 이동하지 않는다.
pr < pl 이 되었으므로 pl과 pr이 교차가 일어나 반복문이 종료된다.
이제 7 3 5 2 4 6 을 퀵정렬해보자.
초기 상태는 pl = 1, pr = 6, pivot = 5 이다.
pl = 1 일 때 5보다 크거나 같은 조건을 만족하므로 더 이상 이동을 하지 않는다.
pr은 피벗인 5보다 작거나 같을 때까지 이동하면 pr = 5 에서 이동을 멈춘다.
arr[1] 과 arr[5] 를 교환한다.
교환이 일어나고, pl = 2로 오른쪽으로 한칸 이동하며 pr = 4로 왼쪽으로 한칸 이동한다.
pl이 5보다 크거나 같을 때까지 이동하면 pl = 3 에서 이동을 멈춘다.
pr은 arr[4] = 2 가 피벗인 5보다 작으므로 더 이상 이동하지 않는다.
arr[3] 과 arr[4] 를 교환한다.
교환이 일어나고, 또다시 pl은 오른쪽으로 pr은 왼쪽으로 이동하게 되면 pr< pl이 되어 반복문이 종료된다.
그리고 left = 1을 시작으로 하고 pr = 3을 끝으로 하는 그룹과, pl = 4을 시작으로 하고 right = 6을 끝으로 하는 그룹으로 나뉘어진다.
왼쪽 그룹부터 먼저 진행해보자.
pl이 가리키는 4은 피벗보다 크고, pr이 가리키는 2는 피벗보다 작으므로
arr[1] 와 arr[3] 을 교환한다.
교환하고 pl은 오른쪽으로 한칸, pr은 왼쪽으로 한칸 이동하면 또 pl과 pr이 이동을 멈춘다.
(pl은 arr[pl]>=pivot 조건을 만족하고, pr은 arr[pr]<=pivot 조건을 만족하므로)
arr[2] 와 arr[2] 를 교환한다.
또 다시 pl은 오른쪽으로 한칸, pr은 왼쪽으로 한칸 이동하면 pr < pl 이 되어 반복문이 종료된다.
나머지 그룹도 똑같이 진행한다.
여태까지 방법과 동일하므로 자세한 설명은 생략한다.
마지막 그룹까지 마치고 나면 최종적으로 배열 arr이 오름차순으로 정렬된 것을 확인할 수 있다.
소스코드 :
public static void quickSort(int[] arr, int left, int right) {
int pl = left;
int pr = right;
int pivot = arr[(pl + pr) / 2];
do {
while (pl <= right && arr[pl] < pivot)
pl++;
while (left <= pr && arr[pr] > pivot)
pr--;
if (pl <= pr)
swap(arr, pl++, pr--);
} while (pl <= pr);
if (left < pr)
quickSort(arr, left, pr);
if (pl < right)
quickSort(arr, pl, right);
}
'Algorithm & DS > 정렬' 카테고리의 다른 글
힙 정렬(Heap Sort) (0) | 2021.02.22 |
---|---|
병합 정렬(Merge Sort) (0) | 2021.02.20 |
단순 삽입 정렬(Straight Insertion Sort) (0) | 2021.02.16 |
단순 선택 정렬(Straight Selection Sort) (0) | 2021.02.16 |
버블 정렬(Bubble Sort) (0) | 2021.02.16 |