[Algorithm] 힙(heap) 이란?


1. 자료구조 힙(Heap)이란?

  • 완전 이진 트리의 일종으로 우선순위 큐를 위하여 만들어진 자료구조이다.
  • 여러 개의 값들 중에서 최댓값이나 최솟값을 빠르게 찾아내도록 만들어진 자료구조이다.
  • 힙은 일종의 반 정렬 상태(느슨한 정렬 상태)를 유지한다.
    • 큰 값이 상위 레벨에 있고 작은 값이 하위 레벨에 있다는 정도
    • 간단히 말하면 부모 노드의 키 값이 자식 노드의 키 값보다 항상 큰(작은)이진 트리를 말한다.
  • 힙 트리에서는 중복된 값을 허용한다. (이진 탐색 트리에서는 중복된 값을 허용하지 않는다.)

2 .힙의 종류

  • 최대 힙(max heap)
    • 부모 노드의 키 값이 자식 노드의 키 값보다 크거나 같은 완전 이진 트리
    • key(부모 노드) >= key(자식 노드)
  • 최소 힙(min heap)
    • 부모 노드의 키 값이 자식 노드의 키 값보다 작거나 같은 완전 이진 트리
    • key(부모 노드) <= key(자식 노드)

힙1

3. 힙(heap) 구현

  • 힙을 저장하는 표준적인 자료구조는 배열이다
  • 구현을 쉽게 하기 위하여 배열의 첫 번째 인덱스인 0은 사용되지 않는다.
  • 특정 위치의 노드 번호는 새로운 노드가 추가되어도 변하지 않는다.
    • 예를 들어 루트 노드의 오른쪽 노드의 번호는 항상 3이다.
  • 힙에서의 부모 노드와 자식 노드의 관계
    • 왼쪽 자식의 인덱스 = (부모의 인덱스) * 2
    • 오른쪽 자식의 인덱스 = (부모의 인덱스) * 2 + 1
  • 시간복잡도: O(N)

힙2

4. 힙(heap) 삽입

  • 합에 새로운 요소가 들어오면, 일단 새로운 노드를 힙의 마지막 노드에 이어서 삽입한다.
  • 새로운 노드를 부모 노드들과 교환해서 힙의 성질을 만족시킨다.
  • 아래의 최대 힙(max heap)에 새로운 요소 8을 삽입
  • 시간 복잡도: O(logN)

힙3

5. 힙(heap)의 삭제

  • 최대 힙에서 최댓값은 루트 노드이므로 루트 노드가 삭제된다.
    • 최대 힙(max heap)에서 삭제 연산은 최댓값을 가진 요소를 삭제하는 것이다.
  • 삭제된 루트 노드에는 힙의 마지막 노드를 가져온다.
  • 힙을 재구성한다.
  • 아래의 최대 힙(max heap)에서 최댓값을 삭제해보자.
  • 시간 복잡도: O(logN)

힙4

Python에서의 힙

Python은 heapq모듈을 통해 heapq를 제공합니다. minheap으로 구현되어 있어 가장 앞에 있는 원소가 가장 작은 원소입니다.

heapq - heapify

  • heapq 모듈의 heapify 함수는 주어진 리스트를 힙 정렬합니다. 이때의 Time Complexity는 O(nlongn)입니다.
# 힙 정렬 예시
import heapq

my_list = [13, 2, 1, 5, 10]
heapq.heapify(my_list)

# 가장 작은 원소인 1이 가장 앞으로 왔습니다.
my_list # [1, 2, 13, 5, 10]

heap - heappop(heap)

  • heap모듈의 heappop 함수는 힙 정렬된 리스트에서
      1. 가장 작은 원소를 빼내고
      1. 나머지 원소가 힙을 유지하도록 정리합니다.
  • 이 함수를 사용할 때에는 주어진 리스트가 힙 정렬되어 있는지 반드시 확인해야 합니다.
  • 정렬되지 않은 리스트에 heappop을 사용하면 이상한 결과가 나옵니다.
import heapq
my_list = [13, 2, 1, 5, 10]
heapq.heapify(my_list)

# 가장 작은 원소인 1이 리턴됩니다. my_list의 길이가 하나 줄어듭니다.
ret_val = heapq.heappop(my_list)

print("리턴된 값:", ret_val) # 1
print("남은 원소:", my_list) # [2, 5, 13, 10]

heap - heappush(heap, item)

  • heap모듈의 heappush 함수는 힙 정렬된 리스트의 힙 상태를 유지하면서 데이터를 삽입시켜줍니다.
  • 이 함수를 사용할 때에는 주어진 리스트가 힙 정렬되어있는지 반드시 확인해야 합니다.
  • 정렬되지 않은 리스트에 heappush를 사용하면 이상한 결과가 나옵니다.
import heapq
my_list = [13, 2, 1, 5, 10]
heapq.heapify(my_list)

# -1 삽입
heapq.heappush(my_list, -1)

# 가장 작은 원소인 -1이 가장 앞에 위치
print("남은 원소:", my_list) # [-1, 2, 1, 5, 10, 13]

Python으로 힙 구현하기

# Implementing "Max Heap"
class MaxHeap:
    def __init__(self, heapSize):
        # Create a complete binary tree using an array
        # Then use the binary tree to construct a Heap
        self.heapSize = heapSize
        # the number of elements is needed when instantiating an array
        # heapSize records the size of the array
        self.maxheap = [0] * (heapSize + 1)
        # realSize records the number of elements in the Heap
        self.realSize = 0

    # Function to add an element
    def add(self, element):
        self.realSize += 1
        # If the number of elements in the Heap exceeds the preset heapSize
        # print "Added too many elements" and return
        if self.realSize > self.heapSize:
            print("Added too many elements!")
            self.realSize -= 1
            return
        # Add the element into the array
        self.maxheap[self.realSize] = element
        # Index of the newly added element
        index = self.realSize
        # Parent node of the newly added element
        # Note if we use an array to represent the complete binary tree
        # and store the root node at index 1
        # index of the parent node of any node is [index of the node / 2]
        # index of the left child node is [index of the node * 2]
        # index of the right child node is [index of the node * 2 + 1]
        parent = index // 2

        # If the newly added element is larger than its parent node,
        # its value will be exchanged with that of the parent node
        while (self.maxheap[index] > self.maxheap[parent] and index > 1):
            self.maxheap[parent], self.maxheap[index] = self.maxheap[index], self.maxheap[parent]
            index = parent
            parent = index // 2

    # Get the top element of the Heap
    def peek(self):
        return self.maxheap[1]

    # Delete the top element of the Heap
    def pop(self):
        # If the number of elements in the current Heap is 0,
        # print "Don't have any elements" and return a default value
        if self.realSize < 1:
            print("Don't have any element!")
            return -sys.maxsize
        else:
            # When there are still elements in the Heap
            # self.realSize >= 1
            removeElement = self.maxheap[1]
            # Put the last element in the Heap to the top of Heap
            self.maxheap[1] = self.maxheap[self.realSize]
            self.realSize -= 1
            index = 1
            # When the deleted element is not a leaf node
            while (index <= self.realSize // 2):
                # the left child of the deleted element
                left = index * 2
                # the right child of the deleted element
                right = (index * 2) + 1
                # If the deleted element is smaller than the left or right child
                # its value needs to be exchanged with the larger value
                # of the left and right child
                if (self.maxheap[index] < self.maxheap[left] or self.maxheap[index] < self.maxheap[right]):
                    if self.maxheap[left] > self.maxheap[right]:
                        self.maxheap[left], self.maxheap[index] = self.maxheap[index], self.maxheap[left]
                        index = left
                    else:
                        self.maxheap[right], self.maxheap[index] = self.maxheap[index], self.maxheap[right]
                        index = right
                else:
                    break
            return removeElement

    # return the number of elements in the Heap
    def size(self):
        return self.realSize

    def __str__(self):
        return str(self.maxheap[1 : self.realSize + 1])


if __name__ == "__main__":
    	# Test cases
        maxHeap = MaxHeap(5)
        maxHeap.add(1)
        maxHeap.add(2)
        maxHeap.add(3)
        # [3,1,2]
        print(maxHeap)
        # 3
        print(maxHeap.peek())
        # 3
        print(maxHeap.pop())
        # 2
        print(maxHeap.pop())
        # 1
        print(maxHeap.pop())
        maxHeap.add(4)
        maxHeap.add(5)
        # [5,4]
        print(maxHeap)

Reference

https://gmlwjd9405.github.io/2018/05/10/data-structure-heap.html