배움 저장소
[Data Structures] 챕터8. 힙 본문
- 최대 힙(Max Heap)과 Top에서 최대값을 출력한다. 최소 힙(Min Heap) Top에서 최소값을 출력한다.
- 보관된 값은 정렬되어있지 않다.
- 트리 개념을 활용하여 배열로 구현한다.
- 최대값 혹은 최소값을 매 번 꺼낼 때 유리한 자료구조이다.
최대 힙(Max Heap)
1. 구현원리
- 완전 이진 트리를 항상 유지한다
- Root 값은 항상 최대값이다
2. 값 저장하기
- 따라서 값을 추가할 때 완전 이진 트리를 유지할 수 있는, 왼쪽에서 비어있는 순서대로 값을 대입한다
- 대입될 노드와 해당 노드의 부모를 비교한다
- 대입될 노드가 부모보다 크다면 자리를 바꾼다(Root까지 반복진행한다)
- 항상 자식이 부모보다 작다
- Swap이 아닌 Shift를 쓰자(새로 만든 노드는 완전 이진 트리를 만들 수 있는 자리에 넣지 않고 비워두고 비교한다)
두 자식의 값을 비교할 수는 없지만 부모는 자식보다 항상 큰 값을 가지므로 효율적으로 최대 값을 찾을 수 있다.
3. 값 꺼내오기
- Root 노드를 꺼낸 뒤, 완전이진트리 기준 최하위 노드를 Root 노드로 가져와 자식과 비교해나간다.
- 가져온 노드를 왼쪽 자식 오른쪽 자식과 비교하여 큰 값과 대체한다
- 대체된 위치에서 1.과 2.를 반복한다
구현하기
- 배열 사용, 0번은 비워 둔다
- 완전이진트리이므로 1번 index부터 해당 index의 위치를 파악할 수 있다.
- 부모 노드를 index로 찾을 수 있다(Index / 2 = parent)
- 자식 노드를 index로 찾을 수 있다(Index*2 = left child, Index*2+1 = right child)
- 값을 추가할 때 배열의 맨 마지막에 넣어준다.
Push
삽입 성능 : O( LogN )
void Push(const T& item)
{
if (size_ == capacity_)
Resize(capacity_ * 2);
// 삽입: 일단 맨 마지막에 삽입한 후에 부모 노드로 올린다.
size_ += 1;
int current = size_; // 마지막에 추가가될 위치 (인덱스) // size_ == last index
// heap_[0] had a trash value
while (current != 1 && heap_[current/2] < item) // 부모 위치의 값이 추가하려는 값보다 작다면
{
heap_[current] = heap_[current / 2]; // 부모 위치의 값을 자식 위치로 복사해서 내린다.
current = current / 2;
}
heap_[current] = item; // 최종적으로 결정된 위치에 복사
}
Pop
삭제 성능 : O( LogN )
void Pop()
{
assert(!IsEmpty());
using namespace std;
cout << "Pop()" << endl;
heap_[1].~T(); // 소멸자 호출
// 삭제: 가장 마지막 값을 루트로 옮긴 후에 내려 보낸다.
T last_item = heap_[size_]; // 마지막 아이템 복사
size_--; // 크기 줄이기
int current = 1; // 루트 노드에서 시작
int child = 2; // current * 2 (루트의 왼쪽 자식 인덱스)
while (child <= size_) // 자식이 있는지 확인
{
if (child < size_) // right child가 존재하는가?
// left, right 중에서 더 큰 자식의 인덱스를 찾는다.
child = heap_[child] < heap_[child+1]? child+1 : child;
// 현재(current) 값이 자식 중 큰 값 보다 크면 더이상 적절한 위치를 찾을 필요가 없다. 루프 중단
if (heap_[child] <= last_item)
break;
// 자식 값을 부모 위치로 복사,
heap_[current] = heap_[child];
// 그 자식 위치로 current 인덱스 변경, child 인덱스도 그 다음 자식 위치로 변경
current = child;
child = current * 2;
}
heap_[current] = last_item;
}
힙정렬
- 힙의 Push와 Pop을 이용하여 정렬할 수 있다.
- 이 때 동일한 값이 왼쪽 자식이나 오른쪽 자식에 들어갈 수 있으므로 들어갔을 때의 순서가 보장되않는다. 따라서 Unstable 정렬이다
- 시간복잡도 : Push/Pop 모두 O(Log N)이므로 N개를 정렬하면 O(N*Log N)이다.
- 공간복잡도 : 배열을 따로 사용할 때 O(N). 힙 배열 내부에서 정렬할 때 O(1)
공간복잡도 O(1) 구현하기
https://www.geeksforgeeks.org/heap-sort/
- 힙에서 꺼낸 가장 큰 값을 배열 마지막 index에 위치한다.
- 마지막 index를 제외한 값으로 1.을 반복한다.
우선순위 큐(Priority Queue)
우선순위 큐는 힙을 사용하여 구현할 수 있다
#include <iostream>
#include "../shared/MaxHeap.h"
using namespace std;
struct Patient
{
int severity; // <- 중증도, 정렬할 때의 1차적인 키(key)로 사용됨
int time; // <- 도착시간, 정렬할 때의 2차적인 키(key)로 사용됨, 중증도가 같을 때는 먼저 온 순서
const char* name;
// 우선순위 관점에서 봤을 때 a >= b 인지를 반환하는 함수
friend bool operator >= (const Patient& a, const Patient& b)
{
if (a.severity == b.severity) // 우선순위가 같을 때는 시간을 기준으로 다시 판단 (선착순)
return a.time <= b.time; // 숫자로써 time이 작은 것이 먼저 왔다는 의미니까 최종 우선순위가 더 높다.
else
return a.severity > b.severity; // 우선순위가 큰 쪽 기준
}
friend bool operator < (const Patient& a, const Patient& b)
{
return !(a >= b); // 앞의 ! 주의 (operator >= 활용)
}
friend bool operator <= (const Patient& a, const Patient& b)
{
if (a.severity == b.severity) // 우선순위가 같을 때는 시간을 기준으로 다시 판단 (선착순)
return a.time >= b.time; // 숫자로써 time이 작은 것이 먼저 왔다는 의미니까 최종 우선순위가 더 높다.
else
return a.severity <= b.severity; // 우선순위가 큰 쪽 기준
}
friend std::ostream& operator << (std::ostream& os, const Patient& p)
{
// TODO: 필요하면 구현
return os;
}
};
int main()
{
// 응급실
MaxHeap<Patient> h;
// 우선순위가 더 높은 환자 먼저 치료
// 더 중증인 경우(severity가 큰 경우) 우선순위가 높다.
// 증상의 무거운 정도가 동일하다면(severity가 같다면) 먼저 온 환자 먼저 치료(time이 작은 환자 먼저)
h.Push({ 1, 0, "Ironman" }); // 중증도 1의 Ironman 시간 0에 도착
h.Push({ 1, 1, "Nick Fury" }); // 중증도 1의 Nick Fury 시간 1에 도착
h.Push({ 3, 2, "Hulk" }); // 중증도 3의 Hulk 시간 2에 도착
cout << h.Top().name << endl; // 중증도가 높은 Hulk 먼저 치료
h.Pop();
cout << h.Top().name << endl; // 중증도가 동일하지만 먼저 도착한 Ironman
h.Pop();
h.Push({ 2, 3, "Blue Beetle" });
cout << h.Top().name << endl; // 늦게 도착했지만 중증도가 높은 Blue Beetle
h.Pop();
cout << h.Top().name << endl; // 마지막으로 Nick Fury
h.Pop();
/* 출력 예시
Hulk
Ironman
Blue Beetle
Nick Fury
*/
return 0;
}
Fixed Priority Queue
#include <cassert>
#include <iostream>
#include <iomanip>
class FixedPriorityQueue;
struct Node
{
int item;
int idx = -1;
Node* link = nullptr;
friend std::ostream& operator << (std::ostream& os, const Node& b)
{
os << b.item;
return os;
}
friend bool operator < (const Node& a, const Node& b)
{
return a.item < b.item;
}
friend bool operator >= (const Node& a, const Node& b)
{
return a.item >= b.item;
}
friend bool operator > (const Node& a, const Node& b)
{
return a.item > b.item;
}
friend bool operator <= (const Node& a, const Node& b)
{
return a.item <= b.item;
}
};
class Heap
{
public:
Heap(int cap = 10)
{
assert(cap > 0);
capacity_ = cap;
size_ = 0;
heap_ = new Node*[capacity_ + 1];
}
void Resize(int new_capacity)
{
Node** new_heap = new Node*[new_capacity];
memcpy(new_heap, heap_, sizeof(Node) * (size_ + 1));
if (heap_) delete[] heap_;
heap_ = new_heap;
capacity_ = new_capacity;
}
Node* Top()
{
return heap_[1];
}
bool IsEmpty()
{
return size_ == 0;
}
void Print()
{
using namespace std;
cout << "Index: ";
for (int i = 1; i <= size_; i++)
cout << setw(3) << i;
cout << endl;
cout << "Value: ";
for (int i = 1; i <= size_; i++)
cout << setw(3) << *heap_[i];
cout << endl;
cout << "idx: ";
for (int i = 1; i <= size_; i++)
cout << setw(3) << heap_[i]->idx;
cout << endl;
cout << "addr: ";
for (int i = 1; i <= size_; i++)
cout << setw(18) << heap_[i];
cout << endl;
cout << "link: ";
for (int i = 1; i <= size_; i++)
cout << setw(18) << heap_[i]->link;
cout << endl << endl;
}
void Push(const Node* item)
{
if (this->size_ == this->capacity_)
Resize(this->capacity_ * 2);
this->size_ += 1;
}
void Pop()
{
assert(!this->IsEmpty());
using namespace std;
cout << "Pop()" << endl;
this->heap_[1]->~Node();
delete this->heap_[1];
}
protected:
Node** heap_ = nullptr;
int size_ = 0;
int capacity_ = 0;
friend FixedPriorityQueue;
};
class MaxHeap : public Heap
{
public:
MaxHeap(int capacity=10) : Heap(capacity){}
void Push(Node* item)
{
Heap::Push(item);
int current = this->size_;
// heap_[0] had a trash value
while (current != 1 && *(this->heap_[current / 2]) < *item)
{
this->heap_[current] = this->heap_[current / 2];
this->heap_[current]->idx = current;
current = current / 2;
}
this->heap_[current] = item;
this->heap_[current]->idx = current;
}
void Pop()
{
Heap::Pop();
Node *last_item = this->heap_[this->size_];
this->size_--;
int current = 1;
int child = 2;
while (child <= this->size_)
{
// Dereferencing it to compare!!!! else compare address
if (child < this->size_ && *(this->heap_[child]) < *(this->heap_[child + 1]))
child = child + 1;
if (*(this->heap_[child]) <= *last_item)
break;
this->heap_[current] = this->heap_[child];
this->heap_[current]->idx = current;
//cout << "Current = " << current << ", child = " << child << endl;
//Print();
current = child;
child = current * 2;
}
this->heap_[current] = last_item;
this->heap_[current]->idx = current;
}
};
class MinHeap : public Heap
{
public:
MinHeap(int capacity=10) : Heap(capacity) {}
void Push(Node* item)
{
Heap::Push(item);
int current = this->size_;
// heap_[0] had a trash value
while (current != 1 && *(this->heap_[current / 2]) > *item)
{
this->heap_[current] = this->heap_[current / 2];
this->heap_[current]->idx = current;
current = current / 2;
}
this->heap_[current] = item;
this->heap_[current]->idx = current;
}
void Pop()
{
Heap::Pop();
Node* last_item = this->heap_[this->size_];
this->size_--;
int current = 1;
int child = 2;
while (child <= this->size_)
{
// Dereferencing it to compare!!!! else compare address
if (child < this->size_ && *(this->heap_[child]) > *(this->heap_[child + 1]))
child = child + 1;
if (*last_item <= *(this->heap_[child]))
break;
this->heap_[current] = this->heap_[child];
this->heap_[current]->idx = current;
//cout << "Current = " << current << ", child = " << child << endl;
//Print();
current = child;
child = current * 2;
}
this->heap_[current] = last_item;
this->heap_[current]->idx = current;
}
};
class FixedPriorityQueue
{
public:
FixedPriorityQueue(int capacity)
{
maxHeap_ = MaxHeap(capacity);
minHeap_ = MinHeap(capacity);
size_ = capacity;
}
void Push(const int& item)
{
using namespace std;
Node *maxNode = new Node{ item };
Node *minNode = new Node{ item };
maxNode->link = minNode;
minNode->link = maxNode;
if (maxHeap_.size_ == size_)
{
// minHeap_.Top()->link is leaf node at maxHeap
int current = minHeap_.Top()->link->idx;
minHeap_.Pop();
minHeap_.Push(minNode);
// compare maxNode with parent
while (current != 1 && *(maxHeap_.heap_[current / 2]) < *maxNode)
{
maxHeap_.heap_[current] = maxHeap_.heap_[current / 2];
maxHeap_.heap_[current]->idx = current;
current = current / 2;
}
maxHeap_.heap_[current] = maxNode;
maxHeap_.heap_[current]->idx = current;
}
else
{
minHeap_.Push(minNode);
maxHeap_.Push(maxNode);
}
}
void Print()
{
using namespace std;
cout << "maxHeap" << endl;
maxHeap_.Print();
cout << "minHeap" << endl;
minHeap_.Print();
}
public:
MaxHeap maxHeap_;
MinHeap minHeap_;
int size_;
};
'CS > Data Structure' 카테고리의 다른 글
[Data Structures] 챕터10. 정렬 (0) | 2023.12.22 |
---|---|
[Data Structures] 챕터9. 이진탐색트리 (0) | 2023.12.21 |
[Data Structures] 챕터7. 트리 (0) | 2023.12.16 |
[Data Structures] 챕터6. 연결 리스트 (0) | 2023.12.13 |
[Data Structures] 챕터5. 큐(Queue) (0) | 2023.11.30 |
Comments