본 포스팅은 아래의 출처를 참고하여 정리한 것입니다.
https://www.youtube.com/watch?v=PIidtIBCjEg&list=PLsMufJgu5933ZkBCHS7bQTx0bncjwi4PK&index=1
장점이 무엇일까
H[0]의 왼쪽 자식 노드=H[1], 오른쪽 자식 노드=H[2]
H[2]의 왼쪽 자식 노드=H[2*2+1]=H[5]=e, 오른쪽 자식 노드=H[2*2+2]=H[6]=f
일반화: H[k]의 왼쪽 자식노드=H[k*2+1], 오른쪽 자식노드=H[k*2+2]
반대로 H[k]의 부모노드=H[(k-2)//2]
장점: 상수 시간 의 연산으로 자식노드와 부모노드의 키값을 알 수 있다!
단점: 비어있는 자리에 대한 불필요한 메모리 공간 낭비
자리를 낭비하지 않으려면 레벨마다 꽉꽉 차있는 형태의 예쁜 모양(마지막 레벨 제외) ➡️ Heap(힙)
다음 예제 트리는 힙 성질을 만족함 ➡️ 힙
힙의 루트 노드: 가장 큰 값
힙의 제공 연산
insert
: 삽입 (힙 성질 만족하도록) ➡️
find_max
: return A[0] (루트노드) ➡️
delete_max
: maximum을 삭제 ➡️
이때 모든 연산은 초기에 make_heap
이 필요함: 주어진 값들을 힙 성질에 맞게 재배치하는 작업
make_heap
: heapify_down 연산을 반복가장 마지막 원소부터 루트까지 차례대로 살펴보면서 밑으로 내려가면서 자리를 찾아가는 것
ex) 11,12,3,15,10은 리프노드로 자식노드가 없기때문에 그자체로 힙 성질 만족
그 다음 1은 1의 왼쪽 자식, 오른쪽 자식을 살펴봄 ➡️ 상수 시간의 키값을 읽는 장점 활용! ➡️ H[k*2+1], H[k*2+2]
def make_heap(A):
n = len(A):
for k in range(n-1, -1, -1):
# A[k] -> heap 성질 만족하도록 내려보내
heapify_down(k, n) # A의 k 위치를 밑으로 내려보내고, n은 힙의 원소 개수
def heapify_down(k, n):
# A{k], n 값
while A[k] != leaf_node:
L, R = 2*k+1, 2*k+2
m = max_index(A[k], A[L], A[R])
if k != m: # 부모노드 키 값 < 자식노드 키 값
A[k] <-> A[m]
k = m
else: # 힙 성질 만족
break
heapify_down
을 k에 대해 n번 호출 ➡️ heapify_down
의 시간을 t라 할 때 의 시간
heapify_down
: 최악의 경우 k 번째 원소가 리프노드까지 도달할 수 있음 ➡️ 가장 오랜 시간이 걸리는 경우는 루트노드에서 가장 밑의 레벨까지 도달하는 경우 ➡️ while문 안에서 max_index 찾고 키 값 변경하는 것은 ➡️ 최악의 경우 힙의 높이(h) 만큼 내려갈 수 있으므로
따라서 =
n개의 노드를 가진 힙의 높이 h는 각 레벨마다 1, 2, 4, 8, ... 노드가 존재
heapify_down
:
make_heap
:
근데 사실 루트노드가 아닌 다른 레벨에 있는 노드에서 heapify_down
하면 시간이 덜 걸리지 ➡️ 어떤 레벨에 있냐에 따라 heapify_down
은 실제로 까지 가능
insert(14): A.append(14)
: 맨 마지막에 키 값 집어넣고 heapify_up
실행
14가 들어옴으로써 힙 성질 만족하지 않으므로 자리 재배치 필요
14를 위로 올려보내면서 자기 자리 찾도록 ➡️ 자신의 부모노드의 값이랑 비교
부모노드보다 자신의 키 값이 더 크므로 자리를 교체
A.heapify_up(k)
: A[k]를 루트노드 방향으로 이동하면서 힙 성질을 만족하도록 heapify 하는 것
def heapify(k): # A[k]를 heapify
while k > 0 and A[(k-1)//2] < A[k]: # root 노드 인덱스에 도달하지 않았고, 부모노드의 키 값이 자식노드의 키 값보다 작으면
A[k], A[(k-1)//2] = A[(k-1)//2], A[k]
k = (k-1) // 2
insert
도 시간 return A[0]
heapify_down
heapify_down(0, n)
def delete_max:
if len(A) == 0: # 비어있는 트리
return None
key = A[0]
A[0], A[len(A)-1] = A[len(A)-1], A[0] # 가장 마지막 리프노드와 swap
A.pop() # 리스트의 가장 마지막 원소 제거
heapify_down(0, len(A))
return key
make_heap
:
insert
:
find_max
:
delete_max
:
heapify_down
:
heapify_up
:
주의할 점은 search
함수가 없음!
즉, 힙은 search를 효율적으로 할 수 있는 자료구조는 아니고, insert
, find_max
, delete_max
를 시간 내에 할 수 있는 특화된 자료구조
find_min
과 delete_min
이 필요할 시 그것을 만족하는 힙을 만들면 됨
max-heap
이라고 함min-heap
: 부모노드는 자식노드보다 항상 값이 작도록 만족하는 힙또한 힙 정렬 heap_sort()
도 가능
n개의 숫자를 입력으로 받고 make_heap
으로 힙 구성:
n 번을 반복하면서 delete_max
를 호출하면 됨
A.pop()
하지말고 그대로 놔두고 다시 heapify해서 적당한 자리로 배치해줌