#힙

최솟값·최댓값을 빠르게 확인하고 우선순위대로 원소를 처리하는 힙 문제를 다룬다. 현재 4개의 풀이가 있다.

힙 학습 가이드

필요한 주제만 펼쳐서 힙의 구조와 우선순위 큐를 활용하는 방법을 학습할 수 있다.

힙 기본 개념

힙은 완전 이진 트리 형태를 유지하면서 부모와 자식 사이의 우선순위 규칙을 지키는 자료구조입니다. 최소 힙에서는 부모가 자식보다 작거나 같고, 최대 힙에서는 부모가 자식보다 크거나 같습니다.

부모 노드가 자식보다 작거나 같은 완전 이진 트리 형태의 최소 힙
최소 힙의 루트에는 전체 원소 중 최솟값이 위치한다.
  • 최소 힙: 최솟값을 루트에서 빠르게 확인
  • 최대 힙: 최댓값을 루트에서 빠르게 확인
  • 완전 이진 트리: 마지막 레벨을 제외한 모든 레벨을 채우고 왼쪽부터 삽입
  • 우선순위 큐: 우선순위가 가장 높은 원소를 먼저 처리하는 추상 자료형

힙은 전체 원소를 정렬하지 않고 현재 가장 작은 값이나 큰 값만 반복해서 꺼내야 하는 문제에 적합하다.

힙의 삽입과 삭제

힙에 원소를 추가할 때는 마지막 위치에 넣은 뒤 부모와 비교하며 위로 올립니다. 루트를 제거할 때는 마지막 원소를 루트로 옮긴 뒤 자식과 비교하며 아래로 내립니다.

힙에 push할 때 sift up하고 pop할 때 sift down하는 동작 비교
삽입과 삭제 모두 트리의 높이만큼만 이동하므로 `O(log n)`에 처리된다.
연산동작시간 복잡도
최솟값·최댓값 확인루트 조회`O(1)`
원소 삽입끝에 추가 후 sift up`O(log n)`
루트 삭제마지막 원소 이동 후 sift down`O(log n)`
전체 힙 구성heapify`O(n)`
우선순위 큐 활용 패턴

현재 후보 중 최솟값 또는 최댓값을 반복해서 선택해야 한다면, 후보 전체를 매번 정렬하는 대신 힙에 후보만 유지한다.

세 개의 정렬 리스트의 현재 원소를 최소 힙에 넣고 최솟값을 꺼낸 뒤 다음 원소를 추가하는 과정
각 리스트의 현재 head만 힙에 넣으면 k개 정렬 리스트의 병합을 효율적으로 수행할 수 있다.
문제 조건힙에 저장할 후보대표 활용
반복해서 최솟값 선택현재 사용 가능한 값k개 정렬 리스트 병합
반복해서 최댓값 선택현재 우선순위가 가장 큰 작업작업 처리와 시뮬레이션
상위 k개만 유지크기 k의 최소 힙k번째 큰 값
거리나 비용이 작은 상태 확장`(비용, 상태)`다익스트라
현재 창의 최댓값·최솟값 관리우선순위와 유효성 정보슬라이딩 윈도우
Python에서 heapq 사용하기
import heapq

heap = []
heapq.heappush(heap, value)
smallest = heap[0]
smallest = heapq.heappop(heap)

Python의 `heapq`는 최소 힙만 제공하므로 최대 힙이 필요하면 음수를 저장한다. 여러 정보를 함께 저장할 때는 튜플의 앞쪽에 우선순위를 두고, 같은 우선순위를 구분할 고유 순번을 추가한다.

sequence = count()
heapq.heappush(heap, (priority, next(sequence), item))

같은 우선순위의 객체가 서로 비교되지 않는 언어·자료형이라면 이 고유 순번이 비교 오류를 막아 줍니다.

자주 하는 실수
  • 힙의 내부 배열이 전체적으로 정렬되어 있다고 생각하는 경우
  • 루트가 아닌 임의의 원소가 전체 최댓값 또는 최솟값이라고 판단하는 경우
  • 최대 힙이 필요한데 음수 변환이나 역순 우선순위를 적용하지 않는 경우
  • 같은 우선순위의 객체를 직접 비교하게 만들어 오류가 발생하는 경우
  • 힙에 오래된 후보를 남겨 두고 현재 상태인지 확인하지 않는 경우

전체 문제 풀이 보기