Data Structure – Heap (Algorithm)

힙(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);
}

Leave a Reply

Your email address will not be published. Required fields are marked *