배열을 원소 하나가 될 때까지 반으로 나눈 뒤, 정렬된 조각 둘을 하나로 합치기를 반복하는 정렬. 원소 하나짜리 배열은 이미 정렬돼 있다는 점에서 출발한다. 분할 정복의 대표 예다.
function merge(a: number[], b: number[]) {
const out: number[] = []
let i = 0, j = 0
while (i < a.length && j < b.length) out.push(a[i] <= b[j] ? a[i++] : b[j++])
return out.concat(a.slice(i), b.slice(j))
}
function mergeSort(arr: number[]): number[] {
if (arr.length <= 1) return arr
const mid = Math.floor(arr.length / 2)
return merge(mergeSort(arr.slice(0, mid)), mergeSort(arr.slice(mid)))
}- 나누는 단계가
log n층이고 층마다 합치는 데O(n)이라 항상O(n log n)이다. 입력 모양에 따라 느려지지 않는다 - 같은 값의 원래 순서가 유지되는 안정 정렬(Stable Sort)이다(
<=덕분) - 새 배열을 만들며 합치므로 추가 메모리
O(n)이 든다. 제자리 병합 정렬(In-Place Merge Sort)도 있지만 훨씬 복잡하다 - 합치기 단계는 정렬된 두 목록을 투 포인터로 훑는 것이다
- 연결 리스트 정렬이나 메모리에 다 안 올라가는 대용량 외부 정렬(External Sorting)에 잘 맞는다