퀵 정렬
이 알고리즘은 먼저 배열의 한 요소를 피벗(pivot)으로 선택한 후, 피벗보다 작은 요소는 모두 피벗의 왼쪽에 위치하도록, 피벗보다 큰 요소는 모두 피벗의 오른쪽에 위치하도록 배열을 분할합니다.
그 다음, 분할된 두 하위 배열을 각각 재귀적으로 퀵 정렬을 수행하여 정렬을 완료합니다.
이 알고리즘의 핵심 아이디어는 분할(Divide) 단계에서 피벗을 기준으로 배열을 분할하여 정복(Conquer) 단계에서 각 하위 배열을 정렬하는 것입니다. 이 과정을 반복하여 분할된 배열이 더 이상 분할되지 않을 때까지 정렬을 수행합니다.
피벗 요소를 기준으로 해당 요소보다 작은 요소는 좌측으로, 큰 요소를 우측으로 분할하여, 배열을 분할한다. 그 후 분할 된 배열 내에서 재귀적 퀵 정렬(퀵 정렬 함수를 다시 호출)을 실행하여, 각 분할된 배열들의 요소가 더 이상 분할이 불가능해질 때 까지 각 배열에 대한 재귀적 퀵 정렬을 반복한다. |
퀵 정렬 코드
function quickSort(arr) {
// 1. 배열의 길이가 1 이하이면 배열을 반환합니다. (기저 조건)
if (arr.length <= 1) {
return arr;
}
// 2. 배열에서 중간 값을 피벗(pivot)으로 선택합니다.
// 편의상 중간값으로 정한 것일 뿐 어느 요소가 피벗이 되어도 상관 없습니다.
// 단, 피벗의 선정 방식에 따라 시간복잡도는 차이가 날 수 있습니다.
// 즉, 어떤 요소를 피벗으로 선택해도 퀵 정렬은 정상적으로 실행됩니다.
const pivotIndex = Math.floor(arr.length / 2);
const pivot = arr[pivotIndex];
const left = [];
const right = [];
/** --- 배열을 left 배열과 right 배열로 분할하는 구간입니다.
3. 배열을 순회하면서, 피벗을 기준으로
'작은 값'은 [왼쪽 배열]에,
'큰 값'은 [오른쪽 배열]에 추가합니다.
*/
for (let i = 0; i < arr.length; i++) {
if (i === pivotIndex) {
// 현재 요소가 피벗이라면, 다음 요소를 처리합니다.
continue;
}
if (arr[i] < pivot) {
// 현재 요소가 피벗보다 작으면, 왼쪽 배열에 추가합니다.
left.push(arr[i]);
} else {
// 현재 요소가 피벗보다 크면, 오른쪽 배열에 추가합니다.
right.push(arr[i]);
}
}
/** 정렬된 각 배열에 대한 정복(합치는) 구간입니다. 정렬될 때 까지 각 배열은 재귀호출을 실시합니다. */
// 4. 왼쪽 배열과 오른쪽 배열을 각각 재귀적으로 `quickSort()` 함수를 호출하여 정렬된 배열을 반환합니다.
return [...quickSort(left), pivot, ...quickSort(right)];
}
시간복잡도와 공간복잡도
퀵 정렬의 시간 복잡도와 공간 복잡도는 피벗의 선택 방법과 분할 시에 어떻게 하위 배열을 나누는지에 따라 달라집니다.
- 최적 시간 복잡도(O(n log n))
최적의 경우, 피벗을 항상 중앙값으로 선택하는 경우입니다. 이 경우, 분할할 때 항상 두 개의 균등한 하위 배열이 생성되므로, 전체 배열을 O(log n)번만큼 분할하면 됩니다. 각 분할 단계에서 배열을 처리하는 데 걸리는 시간은 O(n)이므로, 최적 시간 복잡도는 O(nlogn)입니다.
- 보통 시간 복잡도(O(n log n))
보통의 경우, 피벗의 선택 방법이나 하위 배열을 나누는 방법에 따라 시간 복잡도가 달라집니다. 피벗이 양쪽으로 골고루 분포되어 있는 경우, 분할할 때 항상 균등한 하위 배열이 생성되므로 최적 시간 복잡도와 같습니다.
그러나, 피벗이 최솟값이나 최댓값으로 선택되는 경우, 배열이 한 쪽으로만 쌓이게 되어 분할이 제대로 이루어지지 않을 수 있습니다. 이 경우 시간 복잡도가 O(n^2)까지 증가할 수 있습니다.
이를 방지하기 위해 피벗의 선택 방법을 최적화하는 방법 등의 최적화 기법이 적용됩니다.
- 최악 시간 복잡도(O(n^2))
최악의 경우, 배열이 이미 정렬되어 있는 경우입니다. 이 경우, 피벗을 어떤 값으로 선택하더라도 배열이 한 쪽으로만 쌓이게 되어 분할이 제대로 이루어지지 않습니다.
이 경우, 시간 복잡도는 O(n^2)이 됩니다.
이를 방지하기 위해, 피벗의 선택 방법을 무작위화하는 방법 등의 최적화 기법이 적용됩니다.
퀵 정렬의 공간 복잡도는 보조 배열을 생성하는 방법에 따라 달라집니다.
- 공간복잡도
위의 구현 예제에서는 재귀적으로 호출되는 quickSort() 함수마다 새로운 배열을 생성하므로, 공간 복잡도는 O(n log n)입니다. 하지만, 보조 배열을 한 번만 생성하도록 구현할 수 있다면 공간 복잡도를 O(n)으로 줄일 수 있습니다.
'알고리즘과 자료구조 > 정렬 알고리즘' 카테고리의 다른 글
[알고리즘] 합병정렬 알고리즘(분할과 정복 알고리즘 유형 중 하나) With JS (0) | 2023.03.13 |
---|---|
[알고리즘] 삽입 정렬 with JS (0) | 2023.03.10 |
[알고리즘] 선택 정렬 with JS (0) | 2023.03.09 |
[정렬 알고리즘] 버블정렬 with JS (0) | 2023.03.06 |