알고리즘
[알고리즘] Heap 구조
위시리
2025. 2. 11. 14:20
힙(Heap)이란?
힙(Heap)은 완전 이진 트리(Complete Binary Tree) 기반의 자료구조로, 최댓값 또는 최솟값을 빠르게 찾고 유지하는 데 최적화된 구조이다.
힙의 특징
1. 완전 이진 트리(Complete Binary Tree)
- 왼쪽부터 차례대로 채워지는 이진 트리
- 높이가 최소화되어서 연산 속도가 빠름
2. 힙 속성(Heap Property)
- 최소 힙(Min-Heap): 부모 노드가 자식 노드보다 항상 작음 → heap[0]이 최소값
- 최대 힙(Max-Heap): 부모 노드가 자식 노드보다 항상 큼 → heap[0]이 최대값
- 이 속성 덕분에 최솟값 또는 최댓값을 O(1)에 찾을 수 있다.
3. 탐색보다 삽입/삭제가 빠름
- 일반적인 트리나 리스트는 정렬을 해야 하지만 힙은 O(logN)만에 삽입/삭제 가능
- 다익스트라, 프림 알고리즘 등에서 필수적으로 사용됨
힙의 구조
1. 최소 힙(Min Heap)
1
/ \
3 5
/ \ / \
7 9 8 10
- 부모 ≤ 자식 (최소값이 루트)
- heap[0] = 1 (항상 최소값)
2. 최대 힙(Max Heap)
10
/ \
8 5
/ \ / \
7 9 3 1
- 부모 ≥ 자식 (최대값이 루트)
- heap[0] = 10 (항상 최대값)
힙의 각 연산에 따른 시간 복잡도
삽입 (Insert) | O(logN) |
삭제 (Extract Min/Max) | O(logN) |
최솟값/최댓값 조회 | O(1) |
정렬된 요소 추출 | O(N logN) |
Python에서 힙(Heap) 사용법
1. heapq 모듈 (기본적으로 최소 힙)
heapq를 사용하려면 리스트를 힙처럼 다루어야 한다.heapq는 기본적으로 최소 힙을 제공하므로 최솟값이 먼저 나온다.
import heapq
# 빈 리스트를 힙으로 사용
heap = []
# 요소 추가 (heappush)
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 4)
heapq.heappush(heap, 2)
print(heap) # [1, 2, 4, 3] (내부 구조: 항상 최소값이 루트)
# new heap
heap = []
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 5)
print(heapq.heappop(heap)) # 1 (최소값 제거)
print(heapq.heappop(heap)) # 3
print(heapq.heappop(heap)) # 5
2. 우선순위가 가장 높은 요소(최소값) 꺼내기
min_value = heapq.heappop(heap)
print(min_value) # 1 (가장 작은 값이 제거됨)
print(heap) # [2, 3, 4] (힙이 다시 정렬됨)
3. 여러 개의 값을 한 번에 힙으로 만들기 (heapify)
이미 리스트가 존재할 때, heapify( )를 사용하면 한 번에 최소 힙으로 변환할 수 있다.
nums = [5, 3, 8, 1, 2]
heapq.heapify(nums)
print(nums) # [1, 2, 8, 3, 5] (내부적으로 최소 힙 구조 유지)
1
/ \
2 8
/ \
3 5
4. 최대 힙 만들기 (음수 활용)
Python의 heapq는 최소 힙만 지원하기 때문에 음수 값을 사용하면 최대 힙처럼 동작할 수 있다.
max_heap = []
heapq.heappush(max_heap, -3) # 3을 음수로 변환하여 저장
heapq.heappush(max_heap, -1)
heapq.heappush(max_heap, -4)
heapq.heappush(max_heap, -2)
print(-heapq.heappop(max_heap)) # 4 (가장 큰 값이 나옴)
print(-heapq.heappop(max_heap)) # 3
5. 우선순위가 있는 튜플 사용하기
튜플을 사용하면 우선순위가 낮은 순서대로 정렬된다.
tasks = []
heapq.heappush(tasks, (2, "write report")) # 우선순위 2
heapq.heappush(tasks, (1, "fix bug")) # 우선순위 1 (더 높음)
heapq.heappush(tasks, (3, "email client")) # 우선순위 3
print(heapq.heappop(tasks)) # (1, 'fix bug')
print(heapq.heappop(tasks)) # (2, 'write report')
우선순위가 같은 경우, 입력 순서대로 처리된다.
6. nlargest()와 nsmallest()
최대값이나 최소값을 빠르게 구할 때 heapq.nlargest()와 heapq.nsmallest()를 사용할 수 있다.
nums = [5, 3, 8, 1, 2, 7]
print(heapq.nlargest(2, nums)) # [8, 7] (가장 큰 값 2개)
print(heapq.nsmallest(3, nums)) # [1, 2, 3] (가장 작은 값 3개)
요약
- heapq는 기본적으로 최소 힙(min-heap)을 사용한다.
- heappush(), heappop()을 사용하여 요소를 추가/제거한다.
- heapify()를 사용하면 기존 리스트를 힙으로 변환할 수 있다.
- 최대 힙을 만들려면 음수를 활용한다.
- 튜플을 사용하면 (우선순위, 데이터) 형태로 정렬할 수 있다.
- nlargest()와 nsmallest()로 최댓값/최솟값을 빠르게 찾을 수 있다.
힙을 사용하는 대표적인 문제 유형
1. 최솟값/최댓값을 빠르게 찾는 문제
- 우선순위가 높은 작업을 먼저 처리하는 우선순위 큐
- heapq.heappop()으로 최솟값(또는 최댓값) 즉시 추출 가능
2. K번째 최소/최대값 찾기 (O(k logN))
- heapq.nlargest(k, arr) 또는 heapq.nsmallest(k, arr)
3. 다익스트라 최단 경로 알고리즘 (O((N + E) logN))
- 최단 거리를 우선순위 큐로 관리하여 효율적으로 탐색
4. 프림 알고리즘 (최소 신장 트리)
- 간선의 가중치를 기준으로 정렬하지 않고 O(logN)으로 최적의 선택 가능
힙(Heap)의 핵심 정리
완전 이진 트리 | 왼쪽부터 차곡차곡 채우는 구조 |
최소/최대 힙 | 부모 노드가 항상 자식보다 작거나 큼 |
삽입/삭제가 O(logN) | 정렬 없이도 빠르게 데이터 유지 |
최솟값/최댓값 즉시 추출 (O(1)) | heap[0]에 항상 최소/최대값이 위치 |
우선순위 큐로 활용 | 다익스트라, 작업 스케줄링, K번째 값 찾기에 사용 |
힙은 정렬 없이도 빠르게 우선순위를 관리할 수 있는 강력한 자료구조