큐 (queue) 는 컴퓨터의 기본적인 자료 구조의 한가지로, 먼저 집어넣은 데이터가 먼저 나오는 FIFO 구조로 저장하는 형식을 말한다. 우선순위 큐는 우선순위의 개념을 큐에 도입한 자료구조로, 데이터들이 우선순위를 가지고 있고 우선순위가 높은 데이터가 먼저 나가는 자료구조를 말한다.
queue 속 PriorityQueue 는 Thread-Safe 하고 heapq는 Non-safe 하기 때문이라고 한다.
Thread Safe 하다는 것은 반드시 확인 절차를 걸쳐야 하기 때문에 확인하는 작업떄문에 더 느리다.
코딩테스트로 문제를 푸는 상황이라면 Thread-Safe를 요구하지 않기 때문에 heapq를 써야 시간초과를 면할 수 있을 것이다. 실무에서도 우선순위 큐는 대부분 heapq로 구현하고 있다.
힙 (heap) 은 최댓값 및 최솟값을 찾아내는 연산을 빠르게 하기 위해 고안된 완전이진트리를 기본으로 한 자료구조이다.
A가 B의 부모노드이면, A의 key값과 B의 key값 사이에는 대소관계가 성립한다.(반정렬 상태)
키 값의 대소 관계는 부모/자식 간에만 성립하고, 형제노드 사이에는 대소 관계가 정해지지 않는다.
이진탐색트리(BST)와 달리 중복된 값이 허용된다.
힙에는 '최대 힙' 과 '최소 힙' 이 있다.
최대 힙 : 부모 노드의 키 값이 자식 노드보다 크거나 같은 완전이진트리이다.
❝ key(부모노드) ≥ key(자식노드) ❞
최소 힙 : 부모 노드의 키 값이 자식 노드보다 작거나 같은 완전이진트리이다.
❝ key(부모노드) ≥ key(자식노드) ❞
힙은 가장 높은(혹은 가장 낮은) 우선순위를 가지는 노드가 항상 루트노트에 오게되는 특징이 있으며, 이를 응용하면 우선순위 큐와 같은 추상적 자료형을 구현할 수 있다.
힙은 일반적으로 배열을 이용하여 구현한다. 완전 이진트리이므로 중간에 비어있는 요소가 없기 때문이다.
트리의 각 노드에 번호를 붙이고, 이 번호를 배열의 인덱스로 생각하면 효과적으로 힙을 구현할 수 있다. 배열로 구현하였기 때문에 부모 또는 자식 노드를 찾아가는 연산을 구현하기도 간편하다.
힙의 삽입 : 새로운 요소를 마지막 노드에 이어서 삽입 후, 힙의 성질 만족할 때까지 부모노드와 교환
최악의 경우 새로 추가된 노드가 루트노트까지 비교하며 올라가야 하므로 시간복잡도가 O(log₂n)이다.
힙의 삭제 : (최대 힙의 경우) 루트 노드 삭제 후 마지막 노드를 루트 노드에 위치 시킨 뒤 힙의 성질 만족할 때 까지 자식노드와 교환
삭제 연산 또한 최악의 경우 루트노트부터 가장 아래까지 내려가야 하므로 시간복잡도가 O(log₂n)이다.
파이썬 heapq 모듈은 완전 이진트리 기반의 최소 힙 자료구조이다. 즉 루트노드는 heapq에서 가장 작은 값이다.
import heapq
heap_list = []
heapq.heappush(heap_list, 5)
heapq.heappush(heap_list, 10)
heapq.heappush(heap_list, 99)
heapq.heappush(heap_list, 2)
print(heap_list) # [2, 5, 99, 10]
print(heapq.heappop(heap_list)) # 2
print(heap_list) # [5, 10, 99]
a = heapq.heappop(heap_list)
print(a) # 5
import heapq
h = [ [4, "fourth"], [2, "second"], [3, "third"], [1, "first"]]
heapq.heapify(h)
print(h)
#[[1, 'first'], [2, 'second'], [3, 'third'], [4, 'fourth']]
힙에 원소를 추가할때 튜플 형태로 넣어주면 튜플의 첫 번째 원소를 우선순위로 힙을 구성하게 된다는 아이디어를 이용한다.
(-item, item) 의 튜플 형태로 push 하는 경우 제일 큰 값이 루트노드에 위치하는 최대 힙을 만들 수 있다.
heap_items = [1,3,5,7,9]
max_heap = []
for item in heap_items:
heapq.heappush(max_heap, (-item, item))
print(max_heap)
-> [(-9,9), (-7,7), (-5,5), (-3,3), (-1,1)]
max_item = heapq.heappop(max_heap)[1]