힙이란?
힙: 데이터에서 최대한 최소값을 빠르게 찾기 위해 고안된 완전 이진 트리(Complete Binary Tree)
완전 이진 트리: 노드를 삽입할 때 최하단 왼쪽부터 차례대로 삽입하는 트리
힙을 사용하는 이유
배열에 데이터를 넣고, 최대값과 최소값을 찾으려면 O(n)이 걸림
힙에 데이터를 넣고 최대값과 최소값을 찾으면 O(logn)이 걸림
우선순위 큐와 같이 최대값 또는 최소값을 빠르게 찾아야하는 자료구조 및 알고리즘 구현에 사용된다.
힙(Heap) 구조
힙은 최대값을 구하기 위한 구조(Max Heap)와, 최소값을 구하기 위한 구조(Min Heap)로 분류할 수 있다.
힙은 다음과 같은 두 가지 조건을 가지고 있다.
최대 힙의 경우 각 노드의 값은 해당 노드의 자식 노드가 가진 값보다 크거나 같다.
최소 힙의 경우 각 노드의 값은 해당 노드의 자식 노드가 가진 값보다 작거나 같음
완전 이진 트리 형태를 가진다.
https://ko.wikipedia.org/wiki/%ED%9E%99_(%EC%9E%90%EB%A3%8C_%EA%B5%AC%EC%A1%B0)
힙과 배열
일반적으로 힙 구현시 배열 자료구조를 활용한다.
배열은 인덱스가 0번부터 시작하지만, 힙 구현의 편의를 위해, Root Node의 인덱스 번호를 1로 지정하면 구현이 수월하다.
부모 노드 인덱스 번호(Parent Node index) = 자식 노드 인덱스 번호(Child Node index) // 2
왼쪽 자식 노드 인덱스 번호(Left Child Node index) = 부모 노드 인덱스 번호(Parent Node index) * 2
오른쪽 자식 노드 인덱스 번호(Right Child Node index) = 부모 노드 인덱스 번호(Parent Node index) * 2 + 1
https://ko.wikipedia.org/wiki/%ED%9E%99_(%EC%9E%90%EB%A3%8C_%EA%B5%AC%EC%A1%B0)
힙 구현
class Heap :
def __init__ (self, data ):
self.heap_array = list()
self.heap_array.append(None )
self.heap_array.append(data)
Insert 구현
삽입한 노드가 부모 노드의 값보다 클 경우, 부모 노드와 삽입한 노드 위치를 바꾼다.
def move_up (self, inserted_idx ):
if inserted_idx <= 1 :
return False
parent_idx = inserted_idx // 2
if self.heap_array[inserted_idx] > self.heap_array[parent_idx]:
return True
else :
return False
def insert (self, data ):
if len(self.heap_array) == 0 :
self.heap_array.append(None )
self.heap_array.append(data)
return True
self.heap_array.append(data)
inserted_idx = len(self.heap_array[-1 ])
while move_up(inserted_idx):
parent_idx = inserted_idx // 2
if self.heap_array[inserted_idx] > self.heap_array[parent_idx]:
self.heap_array[inserted_idx], self.heap_array[parent_idx] = self.heap_array[parent_idx], self.heap_array[inserted_idx]
inserted_idx = parent_idx
return True
Insert를 구현할 때에는 조건문을 먼저 타는것이 아니라 데이터를 삽입한 후에 조건문을 이용하여 데이터를 한칸씩 상위노드로 올리는 작업을 해야한다. 최대 힙구조에서는 부모노드가 자식노드보다 작으면 안되기 때문에 삽입되어진 데이터가 부모노드보다 큰지 비교를하여 삽입된 데이터가 더 크다면 서로의 위치를 바꾸어 주는 과정을 반복한다.
Delete 구현
보통 삭제는 Root Node를 삭제한다.(힙의 용도는 최대값, 최소값을 Root Node에 놓아 바로 꺼내 쓸 수 있도록함)
Root Node 삭제시 가장 하단부 왼쪽에 위치한 Node를 Root Node로 이동
Root Node의 값이 Child Node보다 작을 경우 Root Node의 Child Node중 가장 큰 값을 가진 노드와 위치를 바꿔주는 작업을 함
def move_down (self, root_idx ):
left_child_idx = root_idx * 2
right_child_idx = root_idx * 2 + 1
if left_child_idx > len(self.heap_array):
return False
elif right_child_idx >= len(self.heap_array):
if self.heap_array[root_idx] < self.heap_array[left_child_idx]:
return True
else :
return False
else :
if self.heap_array[left_child_idx] > self.heap_array[right_child_idx]:
if self.heap_array[root_idx] < self.heap_array[left_child_idx]:
return True
else :
return False
else :
if self.heap_array[root_idx] < self.heap_array[right_child.idx]:
return True
else :
return False
def pop (self ):
if len(self.heap_array) <= 1 :
return None
returned_data = self.heap_array[1 ]
self.heap_array[1 ] = self.heap_array[-1 ]
del self.heap_array[-1 ]
root_idx = 1
while move_down(root_idx):
left_child_idx = root_idx * 2
right_child_idx = root_idx * 2 + 1
if right_child_idx >= len(self.heap_array):
if self.heap_array[root_idx] < self.heap_array[left_child_idx]:
self.heap_array[root_idx], self.heap_array[left_child_idx] = self.heap_array[left_child_idx], self.heap_array[root_idx]
root_idx = left_child_idx
else :
if self.heap_array[left_child_idx] > self.heap_array[right_child_idx]:
if self.heap_array[root_idx] < self.heap_array[left_child_idx]:
self.heap_array[root_idx], self.heap_array[left_child_idx] = self.heap_array[left_child_idx], self.heap_array[root_idx]
root_idx = left_child_idx
else :
if self.heap_array[root_idx] < self.heap_array[right_child.idx]:
self.heap_array[root_idx], self.heap_array[right_child_idx] = self.heap_array[right_child_idx], self.heap_array[root_idx]
root_idx = right_child_idx
return returned_data
Delete를 구현할 때는 루트노드를 삭제하고 가장 뒤에있는 노드를 끌어올린 이후 부터 조건에 맞게 노드들을 이동시킨다.
데이터를 아래로 이동시키면 세가지 경우가 생긴다.
1. 자식노드가 존재하지 않을때
2. 부모노드아래에 왼쪽자식노드만 존재할 때
3. 자식노드가 둘 다 존재할 때
1번 경우는 자식노드가 없으니 더이상 내려갈 곳이 없어 아무런 행동을 취하지 않아도 된다.
2번경우는 자식노드가 부모노드보다 더 크다면 서로의 위치를 바꾸어 주어야한다.
3번경우는 자식노드 둘을 비교하여 둘중에 더 큰 노드를 부모노드와 비교하여 부모노드보다 더 크다면 위치를 바꿔준다.
이러한 과정을 조건이 맞을 때 까지 반복해준다.
전체 코드
class Heap :
def __init__ (self, data ):
self.heap_array = list()
self.heap_array.append(None )
self.heap_array.append(data)
def move_up (self, inserted_idx ):
if inserted_idx <= 1 :
return False
parent_idx = inserted_idx // 2
if self.heap_array[inserted_idx] > self.heap_array[parent_idx]:
return True
else :
return False
def insert (self, data ):
if len(self.heap_array) == 0 :
self.heap_array.append(None )
self.heap_array.append(data)
return True
self.heap_array.append(data)
inserted_idx = len(self.heap_array[-1 ])
while move_up(inserted_idx):
parent_idx = inserted_idx // 2
if self.heap_array[inserted_idx] > self.heap_array[parent_idx]:
self.heap_array[inserted_idx], self.heap_array[parent_idx] = self.heap_array[parent_idx], self.heap_array[inserted_idx]
inserted_idx = parent_idx
return True
def move_down (self, root_idx ):
left_child_idx = root_idx * 2
right_child_idx = root_idx * 2 + 1
if left_child_idx > len(self.heap_array):
return False
elif right_child_idx >= len(self.heap_array):
if self.heap_array[root_idx] < self.heap_array[left_child_idx]:
return True
else :
return False
else :
if self.heap_array[left_child_idx] > self.heap_array[right_child_idx]:
if self.heap_array[root_idx] < self.heap_array[left_child_idx]:
return True
else :
return False
else :
if self.heap_array[root_idx] < self.heap_array[right_child.idx]:
return True
else :
return False
def pop (self ):
if len(self.heap_array) <= 1 :
return None
returned_data = self.heap_array[1 ]
self.heap_array[1 ] = self.heap_array[-1 ]
del self.heap_array[-1 ]
root_idx = 1
while move_down(root_idx):
left_child_idx = root_idx * 2
right_child_idx = root_idx * 2 + 1
if right_child_idx >= len(self.heap_array):
if self.heap_array[root_idx] < self.heap_array[left_child_idx]:
self.heap_array[root_idx], self.heap_array[left_child_idx] = self.heap_array[left_child_idx], self.heap_array[root_idx]
root_idx = left_child_idx
else :
if self.heap_array[left_child_idx] > self.heap_array[right_child_idx]:
if self.heap_array[root_idx] < self.heap_array[left_child_idx]:
self.heap_array[root_idx], self.heap_array[left_child_idx] = self.heap_array[left_child_idx], self.heap_array[root_idx]
root_idx = left_child_idx
else :
if self.heap_array[root_idx] < self.heap_array[right_child.idx]:
self.heap_array[root_idx], self.heap_array[right_child_idx] = self.heap_array[right_child_idx], self.heap_array[root_idx]
root_idx = right_child_idx
return returned_data