2. Divide & Conquer Algorithm (4) Heap & Heap Sort


Sanjoy Dasgupta가 저술한 책인 Algorithms과 이기창님의 블로그, Heee님의 블로그를 참고하였다.


1. Heap

1-1. Heap(힙)의 정의

쉽게 이해해보자. 만약에 데이터 리스트가 쫘르르~ 있는데, 내가 여기서 가장 큰 값을 찾고 싶으면 일련의 리스트로 구성된 데이터 내에서 힘들게 찾아야 할 것이다. 하지만 만약 데이터가 트리형태로 구성되어 있고, 가장 큰 값이 때마침 루트 노드에 있다면? 당연히 손쉽게 바로 찾을 수 있을 것이다. Heap란 바로 이럴 때 사용하라고 있는 자료구조이다!


cf) Binary Search Tree(BST)

하나의 노드가 최대 두 개의 자식 노드를 갖는 트리. 노드의 값이 반드시 왼쪽 자식노드 $\leq$ 부모노드 $\leq$ 오른쪽 자식노드여야 한다. 참고로 이처럼 트리의 값 구조를 정의하는 방식을 트리순회(Tree Traversal)라고 하며, 완전이진트리는 이중 Inorder방식을 따른다. Heap는 이와 달리 그냥 무조건 부모가 자식보다 크거나 같거나, 작거나 같은 성질을 갖는다.

따라서 BST는 탐색에, Heap은 우선순위 정렬에 주로 사용된다.


Heap의 속성은 다음과 같다.


1-2. Heap(힙)의 종류

Heap는 루트노드가 최대값인 Max heap(최대 힙)과 최소값인 Min heap(최소 힙)으로 구성된다. 이들은 모두 recursive한 구조를 갖고 있다.


1-3. Heap(힙)의 구현(Heapify)

일단 Heap의 표준적인 자료구조는 배열(Array)이다.

따라서 완전이진트리 형태의 Heap을 1차원 배열로 나타내면 다음과 같다. 이처럼 완전이진트리를 배열로 나타내면 가장 좋은 점은 인덱스로 바로 접근이 가능하다는 것이다. 따라서 Heap을 구현할 때, 인덱스를 이용해서 바로 바꿔줄 수 있다.

위의 그림을 통해, 어떤 노드의 index의 왼쪽 자식노드와 오른쪽 자식노드의 index를 각각 left_index, right_index라고 한다면 다음과 같은 특성을 알 수 있다.

left_index = index * 2

right_index = index * 2 + 1


이제부터는 구현에 대해 알아보자. 주어진 자료구조를 Heap로 만드는 연산을 Heapify라고 한다. 예를 들어, Max heap을 만드는 Heapify는 다음과 같다.

앞에서 말했듯이, Max heap은 부모노드의 키 값이 자식노드의 키 값보다 반드시 크거나 같아야 한다. 따라서 첫번째 그림의 4의 경우 자식노드중 인덱스가 먼저인 14와 비교했을 때, 4<14이므로 Max heap의 성질을 만족하지 못한다. 따라서 두번째 그림과 같이 바꿔준다.

그러면 두번째 그림에서 볼 수 있듯이 루트 노드인 14는 자식노드 4, 7보다 크므로 Max heap의 성질을 만족하는 것을 알 수 있다. 이어서 내려온 4는 또, 오른쪽 자식노드 8보다 작은 것을 알 수 있다. 따라서 4와 8을 바꿔주면 세번째 그림이 되고, 이는 Max heap의 성질을 모두 만족하는 것을 알 수 있다.


1-4. Heap(힙)의 삽입, 삭제


1-5. Heap(힙)의 시간복잡도

d진 히프(d-ary Heap)의 경우에는 log의 밑이 2가 아니라 d이다.


2. Heap Sort(힙 정렬)

데이터를 최대 힙 트리나 최소 힙 트리로 구성해 정렬하는 방법.

이제부터는 Max heap을 위주로 설명하려한다. 데이터가 다음과 같이 주어졌다고 하자.

[16, 4, 10, 14, 7, 9, 3, 2, 8, 1]

이를 우선 Insert 방식을 이용해 이진트리로 구성해보면 다음과 같다.

16을 루트노드에 두고 그 다음 4를 왼쪽 자식 노드에, 10을 오른쪽 노드에… 이 과정을 반복한 것이다. 이제 이 트리를 대상으로 heapify를 해보자.

heapify를 할 때 가장 좋은 시작점은 데이터 개수 / 2 의 값을 인덱스로 갖는 노드이다. 이후 이 노드의 자식노드와 값을 비교하며 heapify를 진행한다. 따라서 이 트리의 시작점은 10 / 2 = 5번째 인덱스 노드가 되겠다. 이후 네 번째, 세 번째, 두 번째, 첫 번째 인덱스 노드를 주인공으로 heapify를 진행한다.

이처럼 Heap Sort를 하기 위해서는 먼저 값을 Insert 해서 트리를 만든 뒤, 가운데 인덱스에서부터 위로 올라가며 각각의 자식노드와 값을 비교해 heapify를 하면 된다. 어렵지 않다.


3. Heap Sort(힙 정렬)의 특징


4. Heap Sort(힙 정렬)의 장단점


5. Heap Sort(힙 정렬)의 시간복잡도