Menu
Coddy logo textTech

Heap Sort

마지막 업데이트

힙 정렬은 배열을 이진 힙으로 다룹니다. 먼저 max-heap을 구축하여 가장 큰 원소가 루트(인덱스 0)에 오도록 합니다. 그런 다음 루트를 정렬되지 않은 마지막 원소와 반복해서 교환하여 최댓값을 제자리에 고정하고, 새 루트를 아래로 내려보내(sift down) 힙 속성을 회복합니다. 위의 재생 버튼을 눌러 힙 구축과 추출 과정을 확인하세요.

힙 정렬은 병합 정렬과 마찬가지로 O(n log n) 시간을 보장하지만, 추가 공간을 O(1)만 사용하며 제자리(in-place)에서 정렬합니다. 안정적이지 않고 퀵 정렬보다 캐시 동작이 나쁜 경향이 있어, 보장된 상한과 일정한 메모리가 모두 중요할 때 자주 선택됩니다.

시간 및 공간 복잡도

경우복잡도비고
최선의 경우O(n log n)구축 + n회 추출
평균의 경우O(n log n)무작위 순서
최악의 경우O(n log n)보장됨
공간O(1)제자리
안정성아니요sift-down이 동일한 원소의 순서를 바꿈

단계별 설명

단계무슨 일이 일어나는가
1배열로부터 max-heap을 구축한다(마지막 부모부터 아래로 내려보낸다).
2루트(최댓값)를 힙의 마지막 원소와 교환한다.
3힙을 하나 줄인다 - 그 마지막 자리는 이제 정렬 완료.
4새 루트를 아래로 내려보내 max-heap 속성을 회복한다.
5힙에 원소가 하나 남을 때까지 반복한다.

예제 풀이

[3, 1, 6, 5, 2, 4] 정렬하기. 막대 |는 줄어드는 힙과 정렬된 꼬리 사이의 경계를 나타냅니다:

패스배열동작
힙 구축[6, 5, 4, 1, 2, 3]마지막 부모부터 아래로 내려보내 max-heap을 구축한다. 이제 6이 루트에 있다.
1[5, 3, 4, 1, 2 | 6]루트 6을 마지막 자리와 교환하고, 힙을 줄인 뒤 3을 아래로 내려보낸다.
2[4, 3, 2, 1 | 5, 6]루트 5를 꺼낸 뒤 2를 아래로 내려보내 4가 루트로 올라온다.
3[3, 1, 2 | 4, 5, 6]루트 4를 꺼낸 뒤 1을 아래로 내려보내 3이 루트로 올라온다.
4[2, 1 | 3, 4, 5, 6]루트 3을 꺼낸다. 2는 이미 힙 속성을 만족한다.
5[1 | 2, 3, 4, 5, 6]루트 2를 꺼낸다. 원소가 하나 남으므로 배열이 정렬 완료된다.

힙 정렬을 사용할 때

사용할 때피할 때
O(n²) 위험 없이 보장된 O(n log n) 최악의 경우가 필요할 때.동일한 키의 순서를 보존하는 안정 정렬이 필요할 때.
메모리가 빠듯할 때 - 추가 공간을 O(1)만 쓰며 제자리에서 정렬한다.캐시 성능이 중요하고 데이터가 메모리에 들어갈 때 - 퀵 정렬이 대개 더 빠르다.
이미 데이터 위에 힙(예: 우선순위 큐)을 유지하고 있을 때.비교 횟수를 최소로 하고 싶을 때 - 병합 정렬과 퀵 정렬이 실제로는 더 적게 비교하는 경우가 많다.
신뢰할 수 없는 입력이 퀵 정렬의 최악의 경우를 유발할 수 있고 무작위화할 수 없을 때.데이터가 거의 정렬되어 있을 때 - 삽입 정렬이 그 위에서 거의 선형 시간으로 동작한다.

Heap Sort 코드

Python, JavaScript, Java, C++, C로 작성된 깔끔하고 실행 가능한 Heap Sort 구현입니다. 언어를 선택해 코드를 복사하거나 Coddy 플레이그라운드에서 바로 열어보세요.

Python로 구현한 Heap Sort 코드

Python
1def heap_sort(a):2    n = len(a)3    # Build a max-heap, deepest parent first4    for i in range(n // 2 - 1, -1, -1):5        sift_down(a, i, n)6    # Repeatedly move the max to the end and shrink the heap7    for end in range(n - 1, 0, -1):8        a[0], a[end] = a[end], a[0]9        sift_down(a, 0, end)10    return a11
12
13def sift_down(a, i, size):14    while True:15        largest = i16        left, right = 2 * i + 1, 2 * i + 217        if left < size and a[left] > a[largest]:18            largest = left19        if right < size and a[right] > a[largest]:20            largest = right21        if largest == i:22            return23        a[i], a[largest] = a[largest], a[i]24        i = largest25
26
27nums = [12, 11, 13, 5, 6, 7]28print("Before:", nums)29heap_sort(nums)30print("After: ", nums)
이 코드를 Python 플레이그라운드에서 실행하기

힙 정렬 자주 묻는 질문

힙 정렬의 시간 복잡도는 무엇인가요?
힙 정렬은 최선, 평균, 최악의 경우 모두 O(n log n)입니다. 힙 구축은 O(n)이며 n회의 추출 각각은 O(log n)의 비용이 듭니다. 추가 공간은 O(1)을 사용합니다.
힙 정렬은 안정적인가요?
아니요. sift-down 연산은 동일한 원소를 서로 지나치게 이동시킬 수 있으므로, 힙 정렬은 동일한 키의 상대적 순서를 보존하지 않습니다.
힙 정렬은 언제 사용해야 하나요?
추가 메모리를 O(1)만 쓰면서 보장된 O(n log n) 최악의 경우가 필요할 때 힙 정렬을 사용하세요. 병합 정렬의 O(n) 버퍼 없이 퀵 정렬의 O(n²) 위험을 피할 수 있지만, 그 대가로 안정성과 캐시 성능을 잃습니다.
힙 정렬과 퀵 정렬의 차이는 무엇인가요?
둘 다 제자리에서 정렬하지만, 퀵 정렬은 O(n²)의 최악의 경우가 있는 반면 힙 정렬은 O(n log n)을 보장합니다. 실제로는 퀵 정렬이 캐시 지역성이 좋고 교환이 적어 대개 더 빠르므로, 힙 정렬은 주로 최악의 경우 상한을 보장해야 할 때 선호됩니다.
힙 정렬은 우선순위 큐와 어떻게 관련되나요?
이진 힙은 우선순위 큐의 표준 구현이며, 힙 정렬은 본질적으로 그 큐에서 최댓값을 반복적으로 꺼내는 것입니다. 데이터를 이미 힙으로 유지하고 있다면 원소를 하나씩 꺼내는 것만으로 정렬된 순서를 공짜로 얻을 수 있습니다.
힙 정렬에는 max-heap과 min-heap 중 무엇이 필요한가요?
제자리에서 오름차순으로 정렬하려면 max-heap을 사용하세요. 매 패스마다 가장 큰 원소가 끝으로 교환되어 정렬된 꼬리가 오른쪽에서 자라납니다. min-heap은 제자리에서 내림차순을 만들거나, 별도의 배열로 꺼내면 오름차순을 만듭니다.
Coddy programming languages illustration

Coddy로 알고리즘을 마스터하세요

시작하기