자료구조

MaxHeap(최대 힙) 구현

kkalgo 2022. 9. 28. 17:45

 

1) Root를 항상 최댓값으로 가진다

2) 최대 힙의 구조에서 형제 노드(Sibling Node)끼리는 대소관계가 필요하지 않다. 

   * 삽입 연산 시에는 최대 힙의 '구조'만 가지면 되기 때문에 자식, 즉 깊이(Depth)에서의 대소관계만 필요

   * 삭제 연산 시에는 최대 힙의 구조에서 루트에 항상 최댓값이 오기 때문에 형제 노드끼리의 대소관계도 필요

   → 삽입 시 부모노드와 자식노드만 비교, 삭제 시 부모노드와 자식노드의 형제노드까지 비교 

3) 완전 이진 트리의 형태를 가진다.

   → 깊이의 차이가 1이 넘게 날 수 없다.

       어떠한 부모노드가 왼쪽 자식은 없는데 오른쪽 자식은 있는 경우는 없다. 단, 잎(Leaf) 노드는 상관 없다.

 

* 부모 / 자식간의 인덱스의 참조를 편하게 하기 위해 Root = heap[0]이 아닌 heap[1]로 정의

 

1) Heap_push (힙의 삽입) - vector 자료구조 이용 

void heap_push(int key)
{
	heap.push_back(key);

	if (heap.size() == 2) return;

	int child = heap.size() - 1;
	int parent = child / 2;

	while (child > 1 && heap[parent] < heap[child])
	{
		int temp = heap[parent];
		heap[parent] = heap[child];
		heap[child] = temp;
		child = parent;
		parent = child / 2;
	}
}

- key값을 우선 삽입합니다.

- key값을 삽입했는데 힙의 사이즈가 2라면 현재 힙에 들어와있는 데이터가 1개밖에 없다는 의미입니다. 그대로 종료.

   * heap[0]은 사용하지 않으나, 공간은 차지하고 있는 상태입니다. 

   * 그렇다면 heap[1]이 사실상 첫 번째 공간이 되나, size는 1개가 들어왔을때 [0]의 공간까지 포함 2의 크기를 가집니다.

 

 - child와 parent 정의

   * child는 현재 맨 마지막에 삽입되있으므로 child의 index는 맨 마지막

   * parent는 child / 2

 

 - child가 parent보다 크다면 최대힙의 정의에 위배. 그러므로, parent가 child보다 더 클 수 있도록 계속해서 반복해줍니다.

 

※ 삽입의 연산 횟수는 최대 트리의 높이만큼 이루어집니다. 그러므로, 시간복잡도 = O(logN)

 

 

2) Heap_pop(힙의 삭제)

void heap_pop()
{
	if (heap.size() == 1)return;

	int temp = heap[1];
	heap[1] = heap[heap.size() - 1];
	heap[heap.size() - 1] = heap[1];

	heap.pop_back();

	int parent = 1;
	int child = 2;

	if (child + 1 < heap.size())
	{
		if (heap[child + 1] > heap[child])
			child = child + 1;
	}

	while (child <= heap.size() && heap[child] > heap[parent])
	{
		int temp = heap[parent];
		heap[parent] = heap[child];
		heap[child] = temp;

		parent = child;
		child = parent * 2;

		if (child + 1 < heap.size())
		{
			if (heap[child + 1] > heap[child])
				child = child + 1;
		}
	}
}

- 힙에 원소가 아무것도 들어있지 않다면 삭제연산을 하지 않습니다.

- 최대 힙에서의 삭제

   * Root노드와 마지막 노드를 SWAP → 마지막 노드 삭제 → 힙의 재구성 

- Root를 마지막 노드르 옮겨주고 마지막 노드를 Root로 가져온 후, 마지막 노드를 삭제해줍니다.

 

- Root 노드를 자식과 비교해가며 연산을 수행합니다 → Root노드는 항상 최댓값이 와야 하기 때문에 자식 2명 다 비교

   * heap[parent] < heap[child]라면 parent가 더 클 수 있도록 연산해줍니다.

   * child와 child의 형제 노드 (child + 1)의 대소관계까지 확인해야합니다.

      - 이 작업을 하지 않으면 같은 깊이에 있는 형제 노드 간에 무엇이 더 큰 값인지를 확인하지 않기 때문에 

        루트에 항상 최댓값을 넣을 수 있다는 정의에 위배가 됩니다.

 

 ※ 삭제 연산은 최대 트리의 높이만큼 연산, 시간복잡도 = O(logN)

 

=============================================================================

즉, 삭제와 삽입에 모두 O(logN)의 시간복잡도를 가지는 자료구조입니다.

=============================================================================

 

 

힙 정렬은 어떻게 할까요?

Heap_pop 연산을 한 값을 나열하면 됩니다. 즉, Heap_pop 연산을 계속 하면 됩니다.

 

Heap은 Root에 최댓값을 기록해두는 자료구조입니다.

Heap_pop을 계속해서 한다는 것은, 매 연산이 실행될 때마다 최댓값을 pop한다는 소리이기 때문에

현재 Heap구조에서 최댓값이 내림차순으로 정렬이 됩니다. 

 

하여 Heap정렬이 O(NlogN)의 시간을 가지는 것입니다. N개의 데이터에 대하여 POP을 N번 한 것과 동일!

 

최소힙은 대소관계만 바뀌고, 작은 값이 Root로 오도록 설계해주면 됩니다.