본문으로 건너뛰기
김신건의 로그

Priority Queue / Heap: 우선순위 큐

· 수정 · 📖 약 2분 · 610자/단어 #algorithm #data-structure #heap #priority-queue
Priority Queue / Heap, Priority Queue, Heap, 우선순위 큐, 힙, binary heap, min-heap, max-heap

정의

Priority Queue 는 우선순위가 가장 높은 원소를 O(log N) 에 pop 할 수 있는 자료구조. Binary Heap 이 표준 구현.

  • Max-heap: 부모 >= 자식. top() 이 최대값.
  • Min-heap: 부모 <= 자식. top() 이 최소값.

핵심 연산:

  • push(x): 원소 삽입, O(log N)
  • top(): 최솟값/최댓값 조회, O(1)
  • pop(): 최솟값/최댓값 제거, O(log N)
  • build_heap(arr): 배열에서 힙 구성, O(N) (Floyd’s algorithm)

문제 상황

여러 원소 중 항상 최솟/최댓값을 빠르게 꺼내야 할 때:

자료구조pushtoppop용도
정렬된 배열O(N)O(1)O(N)-
정렬 안 된 배열O(1)O(N)O(N)-
Binary HeapO(log N)O(1)O(log N)Priority Queue
BST (std::set)O(log N)O(log N)O(log N)삭제/탐색 유연

Binary Heap: push/pop 모두 O(log N), top() O(1). 가장 실용적.

시각화

flowchart TD
    A["10 (root, max)"] --> B["7"]
    A --> C["9"]
    B --> D["3"]
    B --> E["5"]
    C --> F["4"]
    C --> G["8"]

부모 인덱스 (i-1)/2, 왼쪽 자식 2i+1, 오른쪽 자식 2i+2.

위 max-heap 을 배열로 표현: [10, 7, 9, 3, 5, 4, 8].

핵심 아이디어

배열 표현

완전 이진 트리를 배열로 compact 하게 저장:

인덱스 0-based:
  부모: (i - 1) / 2
  왼쪽 자식: 2 * i + 1
  오른쪽 자식: 2 * i + 2

포인터 없이 배열만으로 트리 구조 유지. 캐시 친화적.

Heapify Up (push 후)

새 원소를 맨 끝에 추가 후, 부모보다 크면 swap 반복:

heap_push(x):
    arr.append(x)
    i = len(arr) - 1
    while i > 0:
        p = (i - 1) // 2
        if arr[p] < arr[i]:  // max-heap: 부모보다 크면 swap
            swap(arr[p], arr[i])
            i = p
        else:
            break

Heapify Down (pop 후)

루트 제거 후 맨 끝 원소를 루트로 이동, 자식보다 작으면 swap 반복:

heap_pop():
    arr[0] = arr[-1]
    arr.pop()
    i = 0
    while True:
        l = 2*i+1; r = 2*i+2
        largest = i
        if l < len(arr) and arr[l] > arr[largest]: largest = l
        if r < len(arr) and arr[r] > arr[largest]: largest = r
        if largest == i: break
        swap(arr[i], arr[largest])
        i = largest

알고리즘

C++ STL 사용

priority_queue<int> pq;              // max-heap (default)
priority_queue<int, vector<int>, greater<>> mn;  // min-heap

pq.push(5); pq.push(3); pq.push(8);
cout << pq.top() << "\n";   // 8
pq.pop();
cout << pq.top() << "\n";   // 5

// 쌍 (pair): first 기준 정렬
priority_queue<pair<int,int>> pq2;
pq2.push({3, 'a'}); pq2.push({1, 'b'});
// top() = {3, 'a'}

Python heapq (min-heap)

import heapq

pq = []
heapq.heappush(pq, 5)
heapq.heappush(pq, 3)
heapq.heappush(pq, 8)
print(heapq.heappop(pq))  # 3 (min)

# max-heap: 부호 반전
heapq.heappush(pq, -8)
print(-heapq.heappop(pq))  # 8

# heapify: O(N)
arr = [5, 3, 8, 1, 9]
heapq.heapify(arr)
print(heapq.heappop(arr))  # 1

Dijkstra 에서의 활용

Dijkstra 알고리즘에서 min-heap 으로 다음 처리 정점 선택:

priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq;
pq.push({0, src});  // {거리, 정점}
while (!pq.empty()) {
    auto [d, u] = pq.top(); pq.pop();
    if (d > dist[u]) continue;  // 이미 처리된 정점 스킵 (lazy deletion)
    for (auto [v, w] : adj[u])
        if (dist[u] + w < dist[v]) {
            dist[v] = dist[u] + w;
            pq.push({dist[v], v});
        }
}

구현

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int INF = 1e9;

int main() {
  int n, m; cin >> n >> m;
  vector<vector<pii>> adj(n + 1);
  for (int i = 0; i < m; i++) {
      int u, v, w; cin >> u >> v >> w;
      adj[u].push_back({v, w});
      adj[v].push_back({u, w});
  }
  int src = 1;
  vector<int> dist(n + 1, INF);
  dist[src] = 0;
  priority_queue<pii, vector<pii>, greater<pii>> pq;
  pq.push({0, src});
  while (!pq.empty()) {
      auto [d, u] = pq.top(); pq.pop();
      if (d > dist[u]) continue;
      for (auto [v, w] : adj[u]) {
          if (dist[u] + w < dist[v]) {
              dist[v] = dist[u] + w;
              pq.push({dist[v], v});
          }
      }
  }
  for (int i = 1; i <= n; i++)
      cout << (dist[i] == INF ? -1 : dist[i]) << "\n";
  return 0;
}
stdin
4 5
1 2 1
1 3 4
2 3 2
2 4 5
3 4 1
결과
0
1
3
4

복잡도

연산Binary HeapFibonacci HeapPairing Heap
pushO(log N)O(1) amortizedO(1) amortized
topO(1)O(1)O(1)
popO(log N)O(log N) amortizedO(log N) amortized
decrease-keyO(log N)O(1) amortizedO(log log N) amortized
mergeO(N)O(1)O(1)
buildO(N)O(N)O(N)

실전에서는 Binary Heap 이 상수가 작아 가장 빠름. Fibonacci Heap 은 이론적으로만 유리.

변형

변형특징용도
Fibonacci Heapdecrease-key O(1) amortizedDijkstra 이론적 최적 (실전 느림)
Pairing Heap구현 단순, 실전 성능 양호범용
Binomial Heapmerge O(log N)힙 병합 필요 시
d-ary Heapd 개 자식, cache 효율실전 최적화
두 heap (median)max-heap + min-heap[[median-of-stream

함정

WARNING

Lazy Deletion: priority queue 에서 값을 업데이트할 때 기존 항목을 직접 삭제하기 어렵다. 오래된 항목을 pop 할 때 무시하는 lazy deletion 패턴 사용.

WARNING

Python heapq 는 min-heap: max-heap 이 필요하면 값을 부호 반전해서 넣어야 함. (-val, idx) 튜플 활용.

CAUTION

C++ priority_queue 기본값은 max-heap: min-heap 은 greater<> 지정 필수. 혼동하면 Dijkstra 에서 틀림.

흔한 실수

  1. pop() 전에 empty() 확인 안 함 (런타임 에러)
  2. Dijkstra 에서 lazy deletion if d > dist[u] continue 없이 구현
  3. Custom comparator 방향을 반대로 (min-heap 에 less<> 지정)
  4. Python heapq.heappush(pq, (val, obj)) 에서 val 동점 시 obj 비교 오류

BOJ 연습 문제

번호제목키워드
BOJ 1927최솟값min-heap 기본
BOJ 11279최댓값 힙max-heap 기본
BOJ 1655가운데를 말해요max-heap + min-heap
BOJ 7662이중 우선순위 큐최대/최소 모두
BOJ 1916최소 비용 구하기Dijkstra + min-heap
BOJ 23291어항 정리시뮬레이션 + heap

참고

이 글의 용어 (5개)
다익스트라 알고리즘 (Dijkstra's Algorithm)algorithm
정의 다익스트라 알고리즘 (Dijkstra's Algorithm) 은 음이 아닌 가중치 그래프에서 단일 시작점 s 로부터 모든 정점까지의 최단 거리를 찾는 그리디 알고리즘. Ed…
최소 신장 트리 (MST, Minimum Spanning Tree)algorithm
정의 최소 신장 트리 (Minimum Spanning Tree, MST) 는 연결 그래프 G = (V, E) 에서 모든 정점을 연결 하면서 간선 가중치 합이 최소 인 부분 그래프…
Heap Sortalgorithm
정의 Heap Sort (힙 정렬) 는 힙 (heap) 자료구조의 최댓값 / 최솟값을 O(log n) 에 추출 하는 성질을 이용한 정렬. In-place 이고 최악 O(n log…
Median of Stream: 두 힙으로 중앙값 유지algorithm
정의 스트림으로 들어오는 정수의 중앙값을 O(log N) 에 유지하는 문제. 중앙값 = 정렬했을 때 가운데 값. 원소가 짝수 개이면 두 중간 값의 평균. 문제 상황 정수가 하나씩…
Top K Selection: 상위 K개 원소algorithm
정의 배열 또는 스트림에서 상위 K 개 원소를 뽑는 문제. 혹은 K 번째로 큰/작은 원소 하나만 찾는 선택(selection) 문제. - 오프라인: 배열 전체가 주어짐 → Qui…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기