우선순위 큐(Priority Queue)와 Heap의 관계에 대해 알아보겠습니다. 우선순위 큐는 운영체제(OS)에서 프로세스들의 우선순위를 부여하거나, 애플리케이션에서 프리미엄 회원의 요청을 먼저 처리하는 등 우선순위가 있는 대상을 관리할 때 사용됩니다. 이때, 우선순위 큐는 가장 높은 우선순위를 가진 원소를 빠르게 반환하고 삭제할 수 있어야 합니다. 이를 위해 효율적으로 우선순위를 다루는 자료구조로 Heap이 사용됩니다.
Heap은 대표적인 우선순위 큐를 구현하기 위한 자료구조 중 하나로, 특히 Binary Heap이 널리 사용됩니다. Binary Heap은 다음 두 가지 조건을 만족하는 완전 이진 트리입니다.
1. 모든 부모 노드가 자식 노드보다 크거나 같은 값을 갖는다.
2. 완전 이진 트리(Complete Binary Tree) 구조를 유지한다.
이진 힙은 최대 힙(Max heap)과 최소 힙(Min heap)으로 나눌 수 있습니다. 최대 힙은 루트 노드가 가장 큰 값을 갖고, 최소 힙은 루트 노드가 가장 작은 값을 갖습니다.
완전 이진 트리란 모든 레벨이 완전히 채워져 있고, 마지막 레벨에서는 노드들이 왼쪽부터 차례대로 채워진 이진 트리를 의미합니다. 이러한 구조로 인해 완전 이진 트리는 배열로 표현할 수 있어서 효율적인 구현이 가능합니다.
완전 이진 트리의 특성 때문에 각 노드의 위치를 배열의 인덱스로 표현하기 쉽습니다. 루트 노드는 배열의 인덱스 1에 위치하고, 다음 레벨의 노드들은 왼쪽부터 오른쪽으로 순차적으로 인덱스를 할당합니다. 이를 통해 배열 인덱스를 이용하여 트리의 각 노드의 관계를 파악하고 조작할 수 있습니다.
Heapify는 주어진 배열을 힙 구조로 만들기 위한 과정입니다. 완전 이진 트리 구조를 유지하면서 힙 조건을 만족하도록 배열을 조정합니다. 다음과 같은 단계로 이루어집니다:
Max Heap에 새로운 원소를 삽입할 때, 해당 원소는 먼저 배열의 끝에 추가되고, 상위 노드로 이동하며 힙을 조정합니다. 과정은 다음과 같습니다:
최대힙(Max Heap)에서 최대 원소를 삭제할 때는 루트 노드를 삭제하고 반환합니다. 이때, 루트 노드를 삭제하면 완전 이진 트리의 구조가 깨지므로, 맨 끝 노드를 루트로 복사한 뒤 서브 트리를 힙으로 조정합니다.
class Heap:
def __init__(self, capacity):
self.array = [0] * capacity # 배열 초기화
self.n = 0 # 원소 개수 초기화
def push(self, e):
if self.n == len(self.array):
raise Exception("Heap Overflow")
self.array[self.n] = e # 배열의 끝에 원소 추가
self.bubble_up(self.n) # 원소를 올바른 위치로 이동
self.n += 1
def bubble_up(self, i):
child = i
parent = (i - 1) // 2
# 부모와 자식 노드를 비교하며 힙 특성 유지
while parent >= 0 and self.array[child] > self.array[parent]:
self.array[child], self.array[parent] = self.array[parent], self.array[child] # 스왑
child = parent
parent = (child - 1) // 2
def pop(self):
if self.n == 0:
raise Exception("Heap Underflow")
max_value = self.array[0] # 최대값은 루트 노드에 있음
self.array[0] = self.array[self.n - 1] # 루트 노드를 맨 끝 노드로 대체
self.n -= 1
self.bubble_down(0) # 원소를 올바른 위치로 이동
return max_value
def bubble_down(self, i):
parent = i
# 자식 노드와 부모 노드를 비교하며 힙 특성 유지
while True:
left_child = 2 * parent + 1
right_child = 2 * parent + 2
largest = parent
if left_child < self.n and self.array[left_child] > self.array[largest]:
largest = left_child
if right_child < self.n and self.array[right_child] > self.array[largest]:
largest = right_child
if largest == parent:
break
self.array[parent], self.array[largest] = self.array[largest], self.array[parent] # 스왑
parent = largest
def heapify(self):
# 리프노드가 아닌 최초의 노드부터 시작하여 heapify 수행
for i in range((self.n - 2) // 2, -1, -1):
self.bubble_down(i)