힙은 최댓값 및 최솟값을 찾아내는 연산을 빠르게 하기 위해 고안된 완전이진트리를 기반으로 한 자료구조입니다.
힙에는 최대 힙(max heap)과 최소 힙(min heap)이 있습니다. 부모 노드가 자식 노드보다 크면 최대 힙, 반대이면 최소 힙입니다. 가장 큰 숫자가 뿌리에 있게 하려면 최대 힙, 가장 작은 숫자로부터 시작하려면 최소 힙을 사용하면 됩니다.
parent > children ⇒ MAX HEAP
parent < children ⇒ MIN HEAP
은 height와 일치하므로, 트리에 요소가 몇 개 있는지 알면 트리의 높이를 계산할 수 있습니다.
힙에 새로운 데이터를 추가하거나 제거할 때 힙의 규칙을 지켜야 합니다. 최대 힙이면 부모 노드가 자식 노드보다 커야 하고 최소 힙은 자식 노드가 부모 노드보다 커야 합니다.
무언가를 제거할 때 힙에서는 항상 루트를 제거해야 합니다.
// 힙 배열
E[] array = (E[]) new Object[size];
// 마지막 요소 위치 기록
int lastposition;
// 힙 원소 추가
public void add(E obj) {
array[++lastposition] = obj; // 1. 노드 추가
trickleup(lastposition); // 2. trickle up
}
// 힙 원소 교환
public void swap(int f, int t) {
E tmp = array[f];
array[f] = array[t];
array[t] = tmp;
}
// 힙 자식과 부모 크기 비교해서 정렬
public void trickleup(int position) {
if (position == 0)
return;
int parent = (int) Math.floor((position-1)/2)
if (((Comparable<E>) array[position]).compareTo(array[parent])>0) {
swap(position, parent);
trickleup(parent);
}
}
// 힙 원소 삭제
public E remove() {
E tmp = array[0];
// 마지막 인덱스를 줄여주어 실제 삭제한 값은 존재하지만 힙의 일부로 생각 안한다.
swap(0, lastposition--);
trickleDown(0);
return tmp;
}
public void trickleDown(int parent) {
int left = 2 * parent + 1;
int right = 2 * parent + 2;
// 마지막에 왼쪽 자식이 클 때
if (left == lastposition && array[parent] < array[left]) {
swap(parent, left)
return;
}
// 마지막에 오른쪽 자식이 클 때
if (right == lastposition && array[parent] < array[right]) {
swap(parent, right)
return;
}
// 마지막에 부모가 클 때
if (left >= lastposition || right >= lastposition)
return;
// 왼쪽 자식이 클 때 혹은 오른쪽 자식이 클 때
if (array[left] > array[right] && array[parent] < array[left]) {
swap(parent, left);
trickleDown(left);
} else if (array[parent] < array[right]) {
swap(parent, right);
trickleDown(right);
}
}
완전이진트리이기 때문에 노드의 위치는 다음과 같은 성질을 가집니다.
or
힙 규칙에 맞게 숫자의 순서를 맞추는 과정을 힙 정렬 알고리즘이라고 합니다. 랜덤한 순서의 숫자들을 힙으로 만들어 넣고, 힙에서 하나씩 제거하면 되죠. 가장 큰 값 또는 가장 작은 값 순서로 빠지기 때문에 알아서 정렬이 됩니다.
시간 복잡도는 데이터를 넣을 때도 이고, 뺄 때도 이라 고른 성능을 보입니다. N개의 데이터를 모두 빼면 정렬이 되기 때문에 힙 정렬의 시간 복잡도는 이 됩니다.