데이터를 다루다 보면 여러 작업 중 ‘가장 중요한 것’을 빠르게 처리해야 하는 상황이 자주 발생한다.우선순위 큐는 이러한 데이터를 효율적으로 관리하기 위한 자료구조이며 핵심 연산은 다음과 같다.
힙은 우선순위 큐를 효율적으로 구현하기 위해 사용되는 전형적인 자료구조다. 힙은 우선순위 큐를 구현하기 위해 완전 이진트리(complete binary tree)라는 구조를 사용한다.
힙에 값을 추가할 때는 추가하는 값을 리스트의 가장 마지막에 추가한 다음 부모와의 값 비교를 통해 값을 올리는 percolate up 작업을 수행한다.
def percolate_up(A, i):
parent = (i-1)//2
if i > 0 and A[parent] < A[i]:
A[parent], A[i] = A[i], A[parent]
percolate_up(A, parent)
힙에서 값을 삭제할 때는 0번째 인덱스에 위치한 값을 반환하고, 리스트의 마지막에 위치한 값을 0번째 인덱스로 옮겨와서 percolate down 작업을 수행한다.
def percolate_down(A, i):
left = 2*i+1
right = 2*i+2
n = len(A)
largest = i
if left < n and A[largest] < A[left]:
largest = left
if right < n and A[largest] < A[right]:
largest = right
if largest != i:
A[i], A[largest] = A[largest], A[i]
percolate_down(A, largest)
임의의 리스트를 힙으로 만들기 위해서는 트리의 모든 서브트리가 힙 특성을 만족해야 한다. 이를 위해 리프 노드의 부모인 (n-1)//2 인덱스부터 시작해, 각 노드에 대해 percolate_down을 수행한다.
for i in range((n-1)//2, -1, -1):
percolate_down(A, i)
이 방식은 bottom-up 접근으로서, 전체 리스트를 한 번에 힙으로 만든다.
힙을 만드는 방법은 두 가지가 있다.
초기 상태: 빈 힙
리스트의 모든 원소를 하나씩 push
for x in array:
heappush(heap, x)
초기 상태: 리스트 전체가 배열로 전체
아래처럼 한 번에 heapify
i = (n-1)//2 down to 0:
percolate_down(array, i)
heapify는 결국 percolate-down 기반이 훨씬 효율적이기 때문에 bottom-up을 사용한다.
Q) 왜 percolate-up은 비효율적(O(N log N))인가?
Q) 왜 bottom-up + percolate-down은 효율적(O(N))인가?
힙은 완전 이진트리이므로, 루트에서부터의 레벨을 다음과 같이 정의한다.
1. 레벨 k에 있는 노드 수:
k가 커질수록 노드 수가 기하급수적으로 증가한다.
2. 레벨 k가 내려갈 수 있는 최대 percolate-down 깊이
레벨 k의 노드가 내려갈 수 있는 최대 깊이는 H-k이다.
위 레벨일수록 percolate 비용은 크지만 노드 수가 거의 없다.
3. 전체 heapify 비용 공식
레벨 k의 총 비용
전체 heapify 비용: