분할 정복(Divide and Conquer)은 여러 알고리즘의 기본이 되는 해결방법으로, 기본적으로는 엄청나게 크고 방대한 문제를 조금씩 조금씩 나눠가면서 용이하게 풀 수 있는 문제 단위로 나눈 다음 그것들을 다시 합쳐서 해결하자는 개념에서 출발하였다. 대표적으로는 퀵소트나 병합정렬이 있다.
피벗(pivot)을 기준으로 두 개의 부분리스트로 나누어 하나는 피벗보다 작은 값들의 부분리스트, 다른 하나는 피벗보다 큰 값들의 부분리스트로 정렬한 다음, 각 부분리스트에 대해 다시 위 처럼 재귀적으로 수행하여 정렬하는 알고리즘
퀵정렬도 병합정렬과 마찬가지로 분할정복을 통한 정렬방법이다. 병합정렬과의 차이점은 병합정렬은 분할 단계에서는 아무것도 하지않고 병합하는 단계에서 정렬을 수행하지만, 퀵정렬은 분할 단계에서 중요한 작업들을 수행하고 병합시에는 아무것도 하지않는다는 점이다.
function quickSort(arr, left, right) {
if (left < right) {
//기준점을 찾고 기준점을 중심으로 더 작은수, 더 큰수 분류
const i = position(arr, left, right);
//기준점 기준 좌측 정렬
quicksort(arr, left, i - 1);
//기준점 기준 우측 정렬
quicksort(arr, i + 1, right);
}
return arr;
}
function position (arr, left, right) {
let i = left;
let j = right;
const pivot = arr[left];
//제자리 더 큰수/더 작은 수 좌우 배치.
while (i < j) {
while (arr[j] > pivot) j--;
while (i < j && arr[i] <= pivot) i++;
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
arr[left] = arr[j];
arr[j] = pivot;
return j;
}
반으로 나누고 나중에 합치면서 정렬하는 알고리즘
병합정렬은 일종의 분할 정복법 중 하나로 큰 문제를 작은 여러 개의 문제로 쪼개서 각각을 해결한 후 결과를 모아서 원래의 문제를 해결하는 방법이다. 병합이라는 이름 그대로 주어진 자료를 잘게 쪼갠 뒤 합치는 과정에서 정렬을 하는 알고리즘이다.
병합정렬은 항상 O(nlogn)의 시간복잡도를 가지기 때문에 효율적이다. 그러나 원소의 개수만큼 리스트를 쪼개고 따로 저장하고 있어야하기 때문에O(n)의 공간복잡도를 가진다. 한마디로 메모리를 팔아 수행속도를 얻는 경우라고 할 수 있다.
function merge(left, right) {
const sortedArr = [];
while (left.length && right.length) {
//left[0]이 더작을 경우 같을때는 누가 먼저 들어가도 상관X
if (left[0] <= right[0]) {
sortedArr.push(left.shift());
} else {
sortedArr.push(right.shift());
}
}
//left,right 둘 중 하나는 요소가 남아있기 때문에 sortedArr 뒤에 붙여서 출력
//비어있으면 spread Syntax에도 아무것도 없기 때문에 그냥 다 붙여준다.
return [...sortedArr, ...left, ...right];
}
function mergeSort(arr) {
if (arr.length === 1) return arr;
const boundary = Math.ceil(arr.length / 2);
//slice로 해주기 때문에 원본 arr은 손상 없다.
const left = arr.slice(0, boundary);
const right = arr.slice(boundary);
//요소가 1개 일 때까지 재귀를 실행해 요소가 1개일 때 두 left,right부터
//차근차근 merge(정렬해서 합치기)해주면 된다.
return merge(mergeSort(left), mergeSort(right));
}