Merge Sort
정의
Merge Sort (병합 정렬) 는 분할 정복 (Divide & Conquer) 으로 동작하는 비교 정렬. 배열을 반으로 나눠 각각 정렬한 뒤, 두 정렬된 부분을 병합 (merge) 한다.
John von Neumann 이 1945 년에 고안한 알고리즘. 모든 입력에 대해 O(n log n) 을 보장 하고 안정 정렬 인 것이 특징. External Merge Sort 의 기초.
전체 비교는 정렬 알고리즘 참고. RDBMS 에서의 메모리 spill 메커니즘은 정렬·해시는 메모리가 부족하면 어디로 새는가, PGA, work_mem, Workspace Memory 참고.
시각화
알고리즘
세 단계.
- Divide: 배열을 중간에서 둘로 나눈다.
- Conquer: 각 부분을 재귀적으로 Merge Sort.
- Combine (Merge): 정렬된 두 부분을 비교하며 합친다.
mergeSort(arr):
if length(arr) ≤ 1: return arr
mid = length(arr) / 2
left = mergeSort(arr[0..mid])
right = mergeSort(arr[mid..])
return merge(left, right)
merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] ≤ right[j]:
result.push(left[i]); i++
else:
result.push(right[j]); j++
result.push(나머지 left, 나머지 right)
return result
Merge 단계가 핵심
두 정렬된 배열을 합치는 데 선형 시간 이 든다. 각 배열의 맨 앞만 비교하면 되기 때문.
left: [1, 4, 7]
right: [2, 5, 6]
1 vs 2 → 1 채택 result = [1]
4 vs 2 → 2 채택 result = [1, 2]
4 vs 5 → 4 채택 result = [1, 2, 4]
7 vs 5 → 5 채택 result = [1, 2, 4, 5]
7 vs 6 → 6 채택 result = [1, 2, 4, 5, 6]
7 (남음) result = [1, 2, 4, 5, 6, 7]
복잡도
| 항목 | 값 |
|---|---|
| 시간 (최선) | O(n log n) |
| 시간 (평균) | O(n log n) |
| 시간 (최악) | O(n log n) |
| 공간 | O(n) (보조 배열) |
| 안정성 | ✓ Stable |
| In-place | ✗ (보조 배열 필요) |
왜 항상 O(n log n) 인가
재귀 트리의 깊이가 log₂ n. 각 깊이에서 모든 원소가 정확히 한 번씩 merge 됨 (총 n 비교 작업). 따라서 log n × n = n log n 회 작업.
[8, 3, 1, 7, 0, 10, 2, 6]
↓ divide
[8, 3, 1, 7] [0, 10, 2, 6]
↓ divide
[8,3] [1,7] [0,10] [2,6]
↓ divide
[8][3] [1][7] [0][10] [2][6]
↓ merge (level log n)
[3,8] [1,7] [0,10] [2,6]
↓ merge
[1,3,7,8] [0,2,6,10]
↓ merge
[0,1,2,3,6,7,8,10]
IMPORTANT
Quicksort 의 O(n²) 최악 케이스가 없다. 입력이 어떻게 생겼든 정확히 같은 시간이 든다. 응답 시간 보장이 중요한 시스템 (실시간 / DB / 게임 서버) 에서 유리.
In-place 변형
표준 Merge Sort 는 O(n) 보조 메모리를 쓰지만, In-place Merge Sort 도 가능하다. 다만 구현이 복잡하고 상수항이 크다. 실무에서 흔히 쓰는 변형은 다음.
Bottom-up Merge Sort
재귀 대신 반복 으로 구현. 크기 1 → 2 → 4 → … 의 부분 배열을 차례로 머지.
function mergeSortBottomUp(arr) {
const n = arr.length;
const aux = new Array(n);
for (let width = 1; width < n; width *= 2) {
for (let lo = 0; lo < n - width; lo += 2 * width) {
const mid = lo + width - 1;
const hi = Math.min(lo + 2 * width - 1, n - 1);
merge(arr, aux, lo, mid, hi);
}
}
return arr;
}
재귀 호출 스택이 없어 공간 O(log n) → O(1) (보조 배열 외). DB 시스템이 자주 쓰는 패턴.
Natural Merge Sort
입력에서 이미 정렬된 부분 (run) 을 찾아 그것을 단위로 merge. 거의 정렬된 입력에 O(n) 가까운 성능. Timsort 의 기초.
Quicksort 와의 비교
| 항목 | Merge Sort | Quick Sort |
|---|---|---|
| 평균 시간 | O(n log n) | O(n log n) |
| 최악 시간 | O(n log n) 보장 | O(n²) (드물지만 가능) |
| 공간 | O(n) | O(log n) |
| 안정성 | ✓ Stable | ✗ Unstable |
| In-place | ✗ | ✓ (거의) |
| 캐시 친화 | △ (별도 메모리 접근) | ✓ (in-place) |
| 실제 속도 | Quick 보다 조금 느림 | 평균 더 빠름 |
TIP
선택 기준: 안정성이 필요하거나 응답 시간 보장이 필요하면 Merge. 평균 성능과 메모리 효율이 우선이면 Quick. 둘 다 필요하면 Timsort (Merge + Insertion 하이브리드, Python / Java / Rust 표준).
실무 활용
Timsort (Python / Java / Rust)
Tim Peters 가 2002 년 Python 용으로 만든 알고리즘. 현실 데이터에서 자주 보이는 정렬된 부분 (run) 을 활용한 Merge Sort 변형.
# Python
sorted([3, 1, 4, 1, 5, 9, 2, 6]) # Timsort
# Java
Arrays.sort(arr); // 객체 배열은 Timsort
핵심 아이디어:
- 입력에서 자연스러운 run (이미 정렬된 부분) 식별
- run 이 너무 짧으면 Insertion Sort 로 길이 맞춤
- run 들을 Merge Sort 방식으로 합침
- 정렬된 입력에 O(n), 무작위 입력에 O(n log n)
외부 정렬의 기초
External Merge Sort 는 Merge Sort 를 디스크 단위로 확장 한 것:
- “정렬된 부분 배열” → “정렬된 Run 파일”
- “merge 단계” → “K-way Merge” (한 번에 여러 Run 합침)
RDBMS 의 ORDER BY 처리에서 데이터 > 메모리 일 때 사용.
연결 리스트 정렬에 최적
배열은 quicksort 가 빠르지만, 연결 리스트 는 임의 접근이 비싸 quicksort 가 비효율. Merge Sort 는 순차 접근만 하므로 연결 리스트 정렬의 표준.
// 연결 리스트 merge sort (LeetCode 148번)
function sortList(head) {
if (!head || !head.next) return head;
const mid = getMiddle(head);
const right = mid.next;
mid.next = null;
return merge(sortList(head), sortList(right));
}
함정
1. 공간 비용
n=10⁹ 정렬에 추가 메모리 n=10⁹ 필요 → 메모리 부족하면 External Merge Sort 필요.
2. 캐시 미스
In-place 가 아니라 메모리 두 영역 (원본 + 보조) 을 번갈아 접근. 작은 n 에서는 quicksort 보다 캐시 효율이 떨어진다.
3. 작은 부분 배열에서의 비효율
Merge 의 상수항이 크다. 작은 부분 (예: n ≤ 16) 에서는 Insertion Sort 가 훨씬 빠르다. Timsort 가 이 점을 활용한다.
참고
- 정렬 알고리즘
- Quick Sort
- External Merge Sort
- Heap Sort
- Insertion Sort
- 정렬·해시는 메모리가 부족하면 어디로 새는가, PGA, work_mem, Workspace Memory
- Knuth, The Art of Computer Programming, Vol. 3 §5.2.4
이 글의 용어 (5개)
- 정렬 알고리즘algorithm
- 정의 정렬 (sort) 은 원소들의 컬렉션을 어떤 전순서 (total order) 기준으로 재배열하는 것. 알고리즘 입문의 정석 주제이자, 데이터베이스·검색·통계 등 모든 시스템…
- External Merge Sortalgorithm
- 정의 External Merge Sort (외부 머지 정렬) 는 데이터가 메모리에 들어가지 않을 때 사용하는 변형. 두 단계로 동작한다. 1. Run 생성: 메모리 크기만큼 청크…
- Heap Sortalgorithm
- 정의 Heap Sort (힙 정렬) 는 힙 (heap) 자료구조의 최댓값 / 최솟값을 O(log n) 에 추출 하는 성질을 이용한 정렬. In-place 이고 최악 O(n log…
- Insertion Sortalgorithm
- 정의 Insertion Sort (삽입 정렬) 는 한 번에 한 원소씩 이미 정렬된 부분에 올바른 위치를 찾아 삽입 하는 정렬. 카드 정리하듯이 한 장씩 적절한 자리에 끼워넣는 동…
- Quick Sortalgorithm
- 정의 Quick Sort (퀵 정렬) 는 분할 정복 (Divide & Conquer) 기반 비교 정렬. 1959 년 Tony Hoare 가 고안. 배열에서 pivot 을 하나 골…
💬 댓글