개요

Heap(힙) 자료구조는 완전 이진 트리(Complete Binary Tree)를 기반으로 하며, 각 부모 노드가 자식 노드보다 크거나(최대 힙, Max Heap) 작거나(최소 힙, Min Heap) 하는 특성을 가집니다. 이러한 구조 덕분에 힙은 Priority QueueHeap Sort (힙 정렬)과 같은 알고리즘에 많이 사용됩니다.

정의

  • Heap 자료구조:
    • 완전 이진 트리: 모든 레벨이 완전히 채워져 있으며, 마지막 레벨만 오른쪽부터 비워질 수 있음
    • Heap 속성:
      • 최대 힙(Max Heap): 각 부모 노드가 자식 노드보다 크거나 같다.
      • 최소 힙(Min Heap): 각 부모 노드가 자식 노드보다 작거나 같다.
  • 주요 연산:
    • 삽입(Insert): 새로운 요소를 추가한 후 힙 속성을 유지하도록 재구성
    • 삭제(Delete)/추출(Extract): 루트 노드를 제거한 후 힙 재구성
    • 조회(Peek): 루트 노드(최대 힙에서는 최대값, 최소 힙에서는 최소값)를 확인

예제

Python 예제

라이브러리 이용 (heapq 모듈)
import heapq
 
# 최소 힙(min-heap) 사용 예제
heap = []
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 2)
 
print("최소 힙에서 추출한 최소 값:", heapq.heappop(heap))  # 출력: 1
직접 구현
class MinHeap:
    def __init__(self):
        # 힙을 저장할 빈 리스트를 초기화
        self.heap = []
 
    def insert(self, value):
        # 새로운 값을 힙의 마지막에 추가한 후, 힙 속성을 유지하기 위해 bubble up 실행
        self.heap.append(value)
        self._bubble_up(len(self.heap) - 1)
 
    def _bubble_up(self, index):
        # 현재 인덱스의 부모 노드 인덱스 계산
        parent_index = (index - 1) // 2
        # 현재 노드가 루트가 아니며, 부모 노드의 값이 현재 노드보다 큰 경우 교환
        while index > 0 and self.heap[parent_index] > self.heap[index]:
            # 부모 노드와 현재 노드의 값 교환
            self.heap[parent_index], self.heap[index] = self.heap[index], self.heap[parent_index]
            # 인덱스를 부모 인덱스로 업데이트하고, 재계산
            index = parent_index
            parent_index = (index - 1) // 2
 
    def extract_min(self):
        # 힙이 비어있는 경우 None 반환
        if not self.heap:
            return None
        # 힙에 하나의 요소만 있는 경우 해당 요소 반환
        if len(self.heap) == 1:
            return self.heap.pop()
        # 루트(최소값)를 저장하고, 마지막 요소를 루트 위치로 옮김
        root = self.heap[0]
        self.heap[0] = self.heap.pop()
        # 루트에서부터 힙 속성을 복원하기 위해 heapify 실행
        self._heapify(0)
        return root
 
    def _heapify(self, index):
        # 현재 노드와 자식 노드들 중 최소값의 인덱스를 찾음
        smallest = index
        left = 2 * index + 1   # 왼쪽 자식 인덱스
        right = 2 * index + 2  # 오른쪽 자식 인덱스
 
        # 왼쪽 자식이 존재하며, 현재 최소값보다 작으면 smallest 업데이트
        if left < len(self.heap) and self.heap[left] < self.heap[smallest]:
            smallest = left
        # 오른쪽 자식이 존재하며, 현재 최소값보다 작으면 smallest 업데이트
        if right < len(self.heap) and self.heap[right] < self.heap[smallest]:
            smallest = right
 
        # 만약 최소값이 현재 인덱스가 아니라면 값을 교환 후 재귀적으로 heapify 호출
        if smallest != index:
            self.heap[index], self.heap[smallest] = self.heap[smallest], self.heap[index]
            self._heapify(smallest)
 
# 사용 예제
min_heap = MinHeap()
min_heap.insert(3)
min_heap.insert(1)
min_heap.insert(2)
print("직접 구현한 최소 힙에서 추출한 최소 값:", min_heap.extract_min())  # 출력: 1

C++ 예제

라이브러리 이용

#include <iostream>
#include <queue>
#include <vector>
using namespace std;
 
int main() {
    // 기본 priority_queue는 최대 힙(max-heap)입니다.
    priority_queue<int> maxHeap;
    maxHeap.push(3);
    maxHeap.push(1);
    maxHeap.push(2);
    cout << "Max-Heap의 최대 값: " << maxHeap.top() << endl;  // 출력: 3
 
    // 최소 힙(min-heap) 구현: vector와 greater<int>를 사용
    priority_queue<int, vector<int>, greater<int>> minHeap;
    minHeap.push(3);
    minHeap.push(1);
    minHeap.push(2);
    cout << "Min-Heap의 최소 값: " << minHeap.top() << endl;  // 출력: 1
 
    return 0;
}

직접 구현

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
 
// 직접 구현한 최소 힙 클래스
class MinHeap {
private:
    // 힙을 저장할 동적 배열
    vector<int> heap;
 
    // heapifyDown: 부모 노드에서 자식 노드로 내려가며 힙 속성을 복원하는 함수
    void heapifyDown(int index) {
        int smallest = index;              // 현재 인덱스를 최소값으로 초기화
        int left = 2 * index + 1;          // 왼쪽 자식 인덱스 계산
        int right = 2 * index + 2;         // 오른쪽 자식 인덱스 계산
        
        // 왼쪽 자식이 존재하며, 현재 노드보다 작으면 smallest 업데이트
        if (left < heap.size() && heap[left] < heap[smallest])
            smallest = left;
        // 오른쪽 자식이 존재하며, 현재 노드보다 작으면 smallest 업데이트
        if (right < heap.size() && heap[right] < heap[smallest])
            smallest = right;
        
        // 만약 최소값이 현재 노드가 아니라면, 값 교환 후 재귀 호출로 계속 heapify 수행
        if (smallest != index) {
            swap(heap[index], heap[smallest]);
            heapifyDown(smallest);
        }
    }
 
    // heapifyUp: 삽입된 노드가 부모와 비교하여 올바른 위치로 올라가도록 하는 함수
    void heapifyUp(int index) {
        // index가 0이 아니고, 부모 노드의 값이 현재 노드의 값보다 큰 경우
        if (index && heap[(index - 1) / 2] > heap[index]) {
            // 부모와 현재 노드의 값을 교환
            swap(heap[index], heap[(index - 1) / 2]);
            // 부모 인덱스에 대해 재귀적으로 heapifyUp 실행
            heapifyUp((index - 1) / 2);
        }
    }
 
public:
    // insert: 새 값을 힙에 삽입하고, heapifyUp을 호출하여 힙 속성 유지
    void insert(int value) {
        heap.push_back(value);                // 힙의 마지막에 값 추가
        heapifyUp(heap.size() - 1);             // 추가된 값이 올바른 위치로 올라가도록 조정
    }
 
    // extractMin: 루트(최소값)를 추출하고, 힙을 재구성하는 함수
    int extractMin() {
        if (heap.empty()) return -1;          // 힙이 비어있으면 -1 반환 (예외 처리 가능)
        int root = heap[0];                   // 루트(최소값) 저장
        heap[0] = heap.back();                // 마지막 요소를 루트 위치로 이동
        heap.pop_back();                      // 마지막 요소 삭제
        heapifyDown(0);                       // 루트부터 heapifyDown 호출로 힙 속성 복원
        return root;                          // 추출한 최소값 반환
    }
};
 
int main() {
    MinHeap minHeap;
    minHeap.insert(3);
    minHeap.insert(1);
    minHeap.insert(2);
    cout << "직접 구현한 최소 힙에서 추출한 최소 값: " << minHeap.extractMin() << endl;  // 출력: 1
    return 0;
}

연관 페이지

참고 문헌 / 사이트