힙 정렬의 활성 구간과 확정 구간
최댓값을 끝에 옮긴 뒤 루트만 복구하며 전체 정렬을 완성합니다.
최댓값을 반복해서 꺼내면 내림차순으로 얻을 수 있습니다. 최대 힙의 최댓값을 목록 끝에 고정하면 오름차순 정렬도 만들 수 있습니다. 목표는 힙으로 유지할 활성 구간과 이미 정렬된 오른쪽 구간을 구분하는 것입니다. 앞 본문의 인덱스와 sift-down을 복습하세요.
개념과 약속
처음 전체를 최대 힙으로 만듭니다. 루트와 활성 구간의 마지막 원소를 교환하면 최댓값이 최종 위치에 갑니다. 마지막 칸을 활성 구간에서 제외한 뒤 루트의 새 값을 sift_down으로 내려 힙을 복구합니다. 다음 최댓값도 바로 왼쪽에 고정합니다.
end는 오른쪽 확정 구간 앞의 마지막 활성 위치입니다. sift_down(..., end)의 크기는 end 위치를 제외한 칸 수라서, 이제 확정된 최댓값을 다시 움직이지 않습니다. 이 구현은 작업 목록을 복사하지만 그 복사 안에서는 교환과 몇 개 변수로 진행합니다.
손으로 상태 추적하기
| 단계 | 활성 힙 | 오른쪽 확정 구간 |
|---|---|---|
[4,1,3,2]를 힙으로 | [4,2,3,1] | 없음 |
| 루트 4 고정·복구 | [3,2,1] | [4] |
| 루트 3 고정·복구 | [2,1] | [3,4] |
| 루트 2 고정 | [1] | [2,3,4] |
직접 실행하기
아래 코드는 이 본문만으로 실행할 수 있습니다. lesson.py로 저장하고 첫 장에서 확인한 Python 명령으로 실행하세요.
def sift_down(values, root, size):
while 2 * root + 1 < size:
child = 2 * root + 1
if child + 1 < size and values[child + 1] > values[child]:
child += 1
if values[root] >= values[child]:
return
values[root], values[child] = values[child], values[root]
root = child
def build_max_heap(values):
result = list(values)
for root in range(len(result) // 2 - 1, -1, -1):
sift_down(result, root, len(result))
return result
def heap_sort(values):
result = build_max_heap(values)
for end in range(len(result) - 1, 0, -1):
result[0], result[end] = result[end], result[0]
sift_down(result, 0, end)
return result
values = [4, 1, 3, 2]
print(heap_sort(values))
print(heap_sort([2, 2, 1]))
print(heap_sort([]))
print(values)실행 결과:
[1, 2, 3, 4]
[1, 2, 2]
[]
[4, 1, 3, 2]왜 맞고 언제 끝나는가
최대 힙의 루트는 활성 원소 전체의 최대입니다. 끝에 놓으면 그 위치는 올바르고, 다음 최대는 앞선 최대 이하라 오른쪽 구간의 순서도 보존됩니다. 교환 뒤 루트만 조건을 어길 수 있어 아래로 복구하면 충분합니다. 활성 크기가 하나씩 줄어 종료합니다.
비용과 적용 범위
힙 구성 O(n) 뒤 n번에 가까운 삭제·복구 O(log n)를 해서 최악 O(n log n)입니다. 내부 작업은 O(1) 보조 공간이지만 원본 보존 복사는 O(n)입니다. 먼 원소 교환으로 동률 순서를 바꿀 수 있어 일반적인 힙 정렬은 안정적이지 않습니다.
스스로 풀어 보기
[3,1,2]에서 첫 최대를 고정한 뒤 활성 구간과 확정 구간을 쓰세요.- 복구 크기를 전체 길이로 계속 주면 어떤 약속을 깨나요?
힌트
- 3과 마지막 2를 교환한 뒤 앞 두 칸만 봅니다.
- 정답 위치에 둔 값을 다시 힙에 포함합니다.
해설
- 활성
[2,1], 확정[3]입니다. 앞 두 칸은 이미 최대 힙이라 추가 교환이 없습니다. - 이미 확정된 큰 값이 다시 루트로 이동할 수 있습니다. 활성 범위를 줄이는 일은 최적화만이 아니라 정확성의 일부입니다.
흔한 오해
힙을 만든 직후 목록이 정렬됐다고 생각하면 안 됩니다. 루트만 최대이며 전체 순서는 반복적인 추출로 만듭니다.
복습과 다음 연결
다음 절의 그래프 표현에서는 자료 사이의 연결을 표현합니다. 정렬 선택에서 안정성·최악 시간·자료 제약을 함께 비교합니다.