Heap Sort
정의
Heap Sort (힙 정렬) 는 힙 (heap) 자료구조의 최댓값 / 최솟값을 O(log n) 에 추출 하는 성질을 이용한 정렬. In-place 이고 최악 O(n log n) 을 보장.
J.W.J. Williams 가 1964 년 고안. Quicksort 만큼 빠르지는 않지만 응답 시간이 일정하고 메모리 효율이 좋다. LIMIT N 같은 Top-K 문제 에 가장 적합. C++ std::sort (Introsort) 의 폴백 알고리즘.
전체 비교는 정렬 알고리즘 참고.
시각화
알고리즘
heapSort(arr):
buildMaxHeap(arr) // 1단계: 힙 구성
for i = n-1 down to 1:
swap(arr[0], arr[i]) // 2단계: 최댓값을 끝으로
heapifyDown(arr, 0, i) // 새 root 를 내려보내 힙 유지
buildMaxHeap(arr):
for i = n/2 - 1 down to 0:
heapifyDown(arr, i, n)
heapifyDown(arr, i, size):
largest = i
l = 2i + 1
r = 2i + 2
if l < size and arr[l] > arr[largest]: largest = l
if r < size and arr[r] > arr[largest]: largest = r
if largest != i:
swap(arr[i], arr[largest])
heapifyDown(arr, largest, size)
두 단계로 나뉜다
- 힙 빌드 (Build Max Heap): 배열을 max-heap 으로 만든다. O(n).
- 추출 반복 (Extract Max): 루트 (최댓값) 를 배열 끝으로 보내고, 힙 크기를 1 줄인 뒤 heapify. n 번 반복 → O(n log n).
배열로 힙 표현
힙은 별도 트리 구조 없이 배열만으로 표현. 인덱스 i 일 때:
- 부모:
(i - 1) / 2 - 왼쪽 자식:
2i + 1 - 오른쪽 자식:
2i + 2
배열: [9, 7, 8, 3, 5, 6, 4, 1, 2]
트리: 9
/ \
7 8
/ \ / \
3 5 6 4
/ \
1 2
복잡도
| 항목 | 값 |
|---|---|
| 시간 (최선) | O(n log n) |
| 시간 (평균) | O(n log n) |
| 시간 (최악) | O(n log n) |
| 공간 | O(1) |
| 안정성 | ✗ Unstable |
| In-place | ✓ |
왜 buildMaxHeap 이 O(n) 인가
힙 빌드: 리프부터 거꾸로 heapify
- 리프 (n/2 개): heapify 0 회
- 부모 노드 (n/4 개): heapify 1 회
- ...
- 루트 (1 개): heapify log n 회
총 비용 = Σ (n / 2^(h+1)) × h ≤ 2n = O(n)
직관적으로는 O(n log n) 일 것 같지만, 대부분의 노드가 리프 근처라 heapify 비용이 작다.
Quicksort 와의 비교
| 항목 | Heap Sort | Quick Sort |
|---|---|---|
| 평균 시간 | O(n log n) | O(n log n) |
| 최악 시간 | O(n log n) 보장 | O(n²) |
| 공간 | O(1) | O(log n) |
| 안정성 | ✗ | ✗ |
| In-place | ✓ | ✓ |
| 캐시 친화 | △ (인덱스 점프) | ✓ |
| 실측 속도 | Quick 보다 2-3x 느림 | 빠름 |
IMPORTANT
응답 시간 보장이 중요한 시스템 + 메모리 절약 이 필요할 때 Heap. 일반 in-memory 정렬은 Quick 또는 Timsort. Introsort (C++ std::sort) 는 Quick + Heap 의 하이브리드 , 평소엔 Quick, 재귀 깊이가 2 log n 을 넘으면 Heap 으로 폴백.
Top-K (Partial Sort) 에 최적
전체 정렬이 아니라 상위 K 개만 필요하면 Heap 이 압도적이다.
전체 정렬: O(n log n)
Top-K via Heap: O(n log K)
K=10, n=10⁹ 라면 차이가 1000× 가까이 난다.
알고리즘
function topK(arr, k) {
// 크기 K 의 min-heap 유지
const heap = new MinHeap();
for (const x of arr) {
if (heap.size() < k) {
heap.push(x);
} else if (x > heap.peek()) {
heap.pop();
heap.push(x);
}
}
return heap.toArray();
}
각 원소를 한 번씩 보면서 heap 의 최솟값과 비교. heap 크기가 K 로 일정.
RDBMS 의 LIMIT 최적화
SELECT * FROM events ORDER BY ts DESC LIMIT 100;
Oracle / PostgreSQL 모두 이런 쿼리에서 Heap Sort 로 자동 전환. 전체 정렬을 하지 않고 크기 100 의 heap 만 유지한다. 정렬·해시는 메모리가 부족하면 어디로 새는가, PGA, work_mem, Workspace Memory 글에서 “LIMIT N 최적화 시 Heapsort” 라는 한 줄이 이 메커니즘.
우선순위 큐의 자료구조
Heap 은 사실상 [Priority Queue] 의 표준 구현. Dijkstra, A*, K-way merge, scheduler 등 광범위하게 사용.
// JavaScript 에는 빌트인 heap 이 없어 직접 구현
class MinHeap {
constructor() { this.heap = []; }
push(x) { /* heapify up */ }
pop() { /* swap root with last, heapify down */ }
peek() { return this.heap[0]; }
}
Python heapq, Java PriorityQueue, C++ std::priority_queue 모두 heap 기반.
K-way Merge 와의 관계
External Merge Sort 의 K-way merge 단계에서 K 개의 head 중 최솟값을 찾는 데 heap 사용. K=1000 짜리 머지에서 O(log 1000) ≈ 10 회 비교 로 최솟값 결정.
External Merge Sort 의 머지 단계:
for each output position:
min = heap.pop() // O(log K)
write min
heap.push(next from min's run)
함정
1. 안정성 없음
Selection Sort 와 비슷한 이유. heap 추출 시 같은 값의 순서가 보장되지 않음.
2. 캐시 친화성 낮음
배열 인덱스가 i → 2i → 4i → … 로 건너뛰기 때문에 cache miss 가 많다. 같은 O(n log n) 이라도 실측은 Quick / Merge 보다 2-3 배 느리다.
3. 단순 정렬에는 잘 안 쓰임
전체 정렬에서 Heap 을 선호할 일은 드물다. Top-K, Priority Queue, K-way Merge 에서 빛난다.
실무 활용
- C++
std::sort: Introsort 내부에서 폴백 std::partial_sort,std::nth_element: Heap 기반- OS 스케줄러: priority queue
- 그래프 알고리즘: Dijkstra, Prim’s MST
- DB Top-K:
ORDER BY ... LIMIT N - K-way Merge: 외부 정렬
참고
- 정렬 알고리즘
- Quick Sort
- Merge Sort
- Selection Sort
- External Merge Sort
- 정렬·해시는 메모리가 부족하면 어디로 새는가, PGA, work_mem, Workspace Memory
- J.W.J. Williams, “Algorithm 232: Heapsort” (CACM 1964)
- Knuth, TAOCP Vol. 3 §5.2.3
이 글의 용어 (5개)
- 정렬 알고리즘algorithm
- 정의 정렬 (sort) 은 원소들의 컬렉션을 어떤 전순서 (total order) 기준으로 재배열하는 것. 알고리즘 입문의 정석 주제이자, 데이터베이스·검색·통계 등 모든 시스템…
- External Merge Sortalgorithm
- 정의 External Merge Sort (외부 머지 정렬) 는 데이터가 메모리에 들어가지 않을 때 사용하는 변형. 두 단계로 동작한다. 1. Run 생성: 메모리 크기만큼 청크…
- Merge Sortalgorithm
- 정의 Merge Sort (병합 정렬) 는 분할 정복 (Divide & Conquer) 으로 동작하는 비교 정렬. 배열을 반으로 나눠 각각 정렬한 뒤, 두 정렬된 부분을 병합 (…
- Quick Sortalgorithm
- 정의 Quick Sort (퀵 정렬) 는 분할 정복 (Divide & Conquer) 기반 비교 정렬. 1959 년 Tony Hoare 가 고안. 배열에서 pivot 을 하나 골…
- Selection Sortalgorithm
- 정의 Selection Sort (선택 정렬) 는 매 단계에서 남은 원소 중 최솟값을 찾아 맨 앞으로 보내는 정렬. n-1 번의 단계 후 정렬 완료. 다른 O(n²) 정렬과 달리…
💬 댓글