Quick Sort
퀵 소트는 가장 대표적인 정렬 방식 중 하나이며, 또한 대표적인 재귀방식 정렬이기도 하다. 이 포스팅에서는 java를 사용한다.
Code
public void quickSort(int [] arr, int start, int end) {
if(start < end) {
int pivot = findPivot(arr, start, end);
quickSort(arr, start, pivot - 1);
quickSort(arr, pivot+1, end);
}
}
private int findPivot(int[] arr, int start, int end) {
int boundaryOfLow = start - 1;
int pivot = arr[end];
for(int i = start; i<end; i++) {
if(pivot > arr[i]) {
swap(arr, ++boundaryOfLow, i);
}
}
swap(arr, ++boundaryOfLow, end);
return boundaryOfLow;
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}설명
배열이 있다고 가정하자.
int [] array = {3, 5, 9, 4, 6}이 배열에서 한 수를 임의로 추출한다. (이 포스팅에서는 배열의 가장 마지막 수를 추출한다) 위의 코드에서는 6일 것이다.
이제 배열을 앞에서부터 순회하며 6보다 큰 숫자는 오른쪽으로, 6보다 작은 숫자는 왼쪽으로 보낸다. 위의 코드에서 이 역할을 하는 함수가 findPivot 이다.
한번 피봇을 구하면 위의 배열은 이렇게 정돈된다.
int [] array = {3, 5, 4, 6, 9}이렇게 한 다음에, findPivot은 6의 index(위의 코드에서는 3)을 리턴한다.
그러면 이제 0번째 수부터 2 번째 수까지 같은 방식으로 피봇을 뽑아 정렬한다. 3번째 수부터 3번째 수까지 피봇을 뽑아 정렬한다. (퀵소트에서는 숫자가 하나 남으면 정렬된 것으로 본다)
그러면 3,5,4는 {3,4,5} 로 정렬될 것이다. 피봇 4를 기준으로 다시 findPivot을 시도하면 3,5는 각각 숫자가 하나이므로 정렬된 상태이다.
따라서 최종 리턴값은
int [] array = {3, 4, 5, 6, 9}가 된다.
즉, 피봇을 기준으로 피봇보다 작은 숫자는 좌측, 큰 숫자는 우측으로 두는 연산을 배열이 모두 정렬될때까지 수행하는 것이다.
시간 복잡도
결국 퀵소트의 시간 복잡도는 피봇을 얼마나 잘 뽑느냐에 달려 있다. 재귀트리로 증명해보면, 피봇이 배열을 1:9 로 분할해도 시간 복잡도는 O(nlogn)으로 수렴하게 된다.
그러나, 극단적인 상황이 존재하긴 한다. 대표적으로는
int [] arr = {1,2,3,4,5}
int [] arr = {5,4,3,2,1}과 같은 정렬되어 있거나, 역으로 정렬되어 있는 숫자이다. 시간 복잡도를 측정해보면 두 배열 모두 O(n^2)이 된다.
이런 상황을 방지하기 위해, 랜덤하게 피봇을 정할 수도 있다.
- 먼저 배열 내에서 랜덤한 숫자를 하나 고른다.
- 그 숫자를 배열의 가장 마지막 숫자와 swap 한다.
- 배열의 마지막 숫자를 피봇으로 삼아 연산을 수행한다.
이렇게 랜더마이즈하게 퀵소트를 수행할 수 있다.