힙(Heap)
각 노드에 저장되어 있는 값이 자식 노드에 저장되어 있는 값보다 크거나 같은 완전 이진 트리 (최대 힙)
각 노드에 저장되어 있는 값이 자식 노드에 저장되어 있는 값보다 작거나 같은 완전 이진 트리 (최소 힙)

연산 알고리즘
– 삽입
– 최댓값 삭제
– 힙 정렬
– 힙 정렬 (공간복잡도를 최소화)
– heapify (완전 이진트리를 힙으로 만드는 연산)
시간복잡도
삽입: O(logn) – 모든 노드와 SWAP한 경우 (최악), O(1) – SWAP 하지 않음 (최선)
최댓값 삭제: O(logn) – 루트부터 한 레벨 씩 내려가면서 SWAP하기 때문
정렬: O(nlogn) – 최댓값 삭제를 n번해서 배열에 거꾸로 저장하기 때문
heapify: O(n) – 리프가 아닌 모든 노드로부터 리프까지 거리의 합만큼 SWAP을 진행할 수 있다. 이 때 거리의 합은 O(n)
힙은 ‘삽입 후 최댓값 삭제’를 가장 효율적으로 할 수 있는 자료구조이다!
시간 복잡도: O(logn), 힙 사용하지 않을 시 최소 O(n)
최대 힙의 삽입,삭제,정렬 소스 (Insertion, Max_delete, Sorting)
#include <stdio.h>
#define MAX_ELEMENT 200
#define size 8
typedef struct {
int key;
}element;
typedef struct {
element heap[MAX_ELEMENT];
int heap_size; // 힙 사이즈가 n이면 heap배열의 원소들이 (idx 1 ~ idx n) 까지 존재
}HeapType;
typedef HeapType* HeapType_ptr;
// 생성 함수
HeapType_ptr create() {
return (HeapType*)malloc(sizeof(HeapType));
}
// 초기화 함수
void init(HeapType_ptr h) {
h->heap_size = 0;
}
// 현재 요소의 개수가 heap_size인 히프 h에 item을 삽입한다.
// 삽입 함수
void insert_max_heap(HeapType_ptr h, element item) {
int i;
i = ++(h->heap_size);
// 트리를 거슬러 올라가면서 부모 노드와 비교하는 과정
// 배열로 트리를 구현하니까 부모 노드의 idx는 1
while ((i != 1) && (item.key > h->heap[i / 2].key)) {
h->heap[i] = h->heap[i / 2]; // 부모를 한 칸 내림
i /= 2;
}
h->heap[i] = item; // 삽입될 위치가 확실해진 다음에 새로운 노드를 삽입
}
// 삭제 함수
element delete_max_heap(HeapType_ptr h) {
int parent, child;
element item, temp;
item = h->heap[1]; // 루트 노드 값을 반환하기 위해 item변수로 옮긴다.
temp = h->heap[(h->heap_size)--]; // 말단 노드 값을 temp변수로 옮기고 힙 사이즈를 1 줄인다. (처음에는 루트에 위치해있다고 생각)
parent = 1;
child = 2; // 루트의 왼쪽 자식부터 비교한다.
// 자식이 힙을 벗어나지 않았으면 반복문 진행
while (child <= h->heap_size) {
// 두 자식노드가 있으면 더 큰 자식노드를 찾는다.
if ((h->heap[parent * 2].key >= 0) && (h->heap[(parent * 2) + 1].key >= 0)) {
if ((h->heap[parent * 2].key) > (h->heap[(parent * 2) + 1].key))
child = parent * 2;
else
child = (parent * 2) + 1;
}
// 말단 노드를 현재 자식의 부모 노드의 위치에 있다고 생각했을 때, 값이 크거나 같으면 힙 조정할 필요가 X
if (temp.key >= h->heap[child].key)
break;
// 부모 노드 자리에 자식 노드의 키 값을 저장
h->heap[parent] = h->heap[child];
// 한 단계 아래로 이동
parent = child;
child *= 2;
}
// 말단 노드를 현재 자식의 부모 노드의 위치에 있다고 생각했을 때, 자식 값이랑 크거나 같음 -> 실제로 부모 노드에 저장
// (Or) child>h->heap_size로, 위치해야 될 노드에 저장
h->heap[parent] = temp;
return item;
}
// 힙 정렬
void heap_sort(element a[], int n) {
int i;
HeapType_ptr h = create();
init(h);
// 리스트 원소들을 힙에 저장
for (i = 0; i < n; i++)
insert_max_heap(h, a[i]);
// 힙 배열의 큰 원소부터 리스트에 거꾸로 저장
for (i = (n - 1); i >= 0; i--)
a[i] = delete_max_heap(h);
free(h);
}
int main(void) {
element list[size] = { 23,56,11,9,56,99,27,34 };
HeapType_ptr heap = create(); // 힙 생성
init(heap); // 초기화
// 삽입
for (int i = 0; i < size; i++)
insert_max_heap(heap, list[i]);
// 삭제
printf("힙 삭제: ");
for (int i = 0; i < size; i++)
printf("%d ", (delete_max_heap(heap)).key);
printf("\n");
// 힙을 이용해서 정렬된 리스트 만들기
printf("힙 정렬: ");
heap_sort(list, size);
for (int i = 0; i < size; i++)
printf("%d ", list[i].key);
printf("\n");
return 0;
}

Heapify로 최대 힙 만들기 소스
#include <stdio.h>
#include <time.h>
#define MAX_ELEMENT 100
#define size 99
typedef struct {
int key;
}element;
typedef struct {
element heap[MAX_ELEMENT];
int heap_size; // 힙 사이즈가 n이면 heap배열의 원소들이 (idx 1 ~ idx n) 까지 존재
}HeapType;
typedef HeapType* HeapType_ptr;
// 생성 함수
HeapType_ptr create() {
return (HeapType*)malloc(sizeof(HeapType));
}
// 초기화 함수
void init(HeapType_ptr h) {
h->heap_size = 0;
}
// 리스트로 힙 만들기
void heapify(HeapType_ptr h, element* a) {
// 리스트 a를 '완전 이진 트리 배열'로 생각한 뒤 h에 일단 저장
h->heap_size = size;
for (int i = 1; i <= h->heap_size; i++)
h->heap[i] = a[i];
// -> 힙 성질을 만족하지 않는 h가 생성됨
// 힙 성질을 만족하게 할 첫 번째 서브트리 지정
int parent, child, parent_save, child_save;
child = child_save = h->heap_size; // 말단 노드부터 비교 시작
parent = parent_save = h->heap_size / 2;
// 모든 서브트리에 대해 힙 성질 만족 시키기
element temp;
while (parent > 0) {
// 현재 부모 노드의 값을 temp로 지정
temp = h->heap[parent];
// 자식이 힙을 벗어나지 않았으면 반복문 진행
while (child <= h->heap_size) {
// 두 자식노드가 있으면 더 큰 자식노드를 찾는다.
if ((h->heap[parent * 2].key >= 0) && (h->heap[(parent * 2) + 1].key >= 0)) {
if ((h->heap[parent * 2].key) > (h->heap[(parent * 2) + 1].key))
child = parent * 2;
else
child = (parent * 2) + 1;
}
// temp 노드를 현재 자식의 부모 노드의 위치에 있다고 생각했을 때, 값이 크거나 같으면 힙 조정할 필요가 X
if (temp.key >= h->heap[child].key)
break;
// 부모 노드 자리에 자식 노드의 키 값을 저장
h->heap[parent] = h->heap[child];
// 한 단계 아래로 이동
parent = child;
child *= 2;
}
// child>h->heap_size로 말단 노드에 저장
h->heap[parent] = temp;
// 서브트리 재지정
parent = parent_save = parent_save - 1;
child = child_save = parent * 2; // p가 1이고 c가2면 전체 노드를 다 보게 됨
}
}
// 삭제 함수
element delete_max_heap(HeapType_ptr h) {
int parent, child;
element item, temp;
item = h->heap[1]; // 루트 노드 값을 반환하기 위해 item변수로 옮긴다.
temp = h->heap[(h->heap_size)--]; // 말단 노드 값을 temp변수로 옮기고 힙 사이즈를 1 줄인다. (temp값이 처음에는 루트에 위치해있다고 생각)
parent = 1;
child = 2; // 루트의 왼쪽 자식부터 비교한다.
// 자식이 힙을 벗어나지 않았으면 반복문 진행
while (child <= h->heap_size) {
// 두 자식노드가 있으면 더 큰 자식노드를 찾는다.
if ((h->heap[parent * 2].key >= 0) && (h->heap[(parent * 2) + 1].key >= 0)) {
if ((h->heap[parent * 2].key) > (h->heap[(parent * 2) + 1].key))
child = parent * 2;
else
child = (parent * 2) + 1;
}
// 말단 노드를 현재 자식의 부모 노드의 위치에 있다고 생각했을 때, 값이 크거나 같으면 힙 조정할 필요가 X
if (temp.key >= h->heap[child].key)
break;
// 부모 노드 자리에 자식 노드의 키 값을 저장
h->heap[parent] = h->heap[child];
// 한 단계 아래로 이동
parent = child;
child *= 2;
}
// 말단 노드를 현재 자식의 부모 노드의 위치에 있다고 생각했을 때, 자식 값이랑 크거나 같음 -> 실제로 부모 노드에 저장
// (Or) child>h->heap_size로, 위치해야 될 노드에 저장
h->heap[parent] = temp;
return item;
}
int main(void) {
// 랜덤값 size개 이용해서 리스트 생성 <인덱스 1부터 size까지 저장>
element* list = (element*)malloc(sizeof(element) * (size + 1));
for (int i = 1; i <= size; i++)
list[i].key = rand() % 100;
HeapType_ptr heap = create(); // 힙 생성
init(heap); // 초기화
// 힙 제작 [시간복잡도: O(n)]
heapify(heap, list);
// 삭제
printf("힙 삭제: [ Original Size:%d ]", heap->heap_size);
for (int i = 1; i <= size; i++)
printf("\n%d) %d, size:%d", i, (delete_max_heap(heap)).key, heap->heap_size);
printf("\n");
free(heap);
}
