- 원소의 추가 : O(log n)
- 우선순위가 가장 높은 원소의 확인 : O(1)
- 우선순위가 가장 높은 원소의 제거 : O(log n)
=> "힙(Heap)" 을 사용해서 구현함!
힙의 삽입 연산 : O(log n)
최솟값이 위치한 노드 = 루트 노드
=> 루트 노드를 리턴하면 끝이다 => O(1)
반대로 최댓값을 찾는것은? => 최댓값을 가진 노드는 말단 노드(leaf node) 중에서 하나겠지만, 말단 노드들을 모두 일일이 다 확인하는 것은 힘들기도 하고 불가능하다고 보는게 좋다!
루트 노드와 리프 노드의 가장 오른쪽 맨끝 노드를 서로 위치를 바꾼다.
맨 마지막 위치로 이동한 노드 (기존 루트 노드) 를 삭제시킨다.
다운힙(Down-Heap) 을 수행 => 순서 조건을 만족시키기 위해, 다운힙을 수행
(=> 흽의 높이만큼, 즉 O(log n) 만큼 다운힙을 수행)
==> 즉, 루트 노드 위치로 이동한 기존 리프 노드의 값을 순서 조건을 만족시키기 위해 게속 이동시킴!
#include <bits/stdc++.h>
using namespace std;
int heap[100005];
int sz = 0; // 힙에 들어있는 원소의 수
void push(int x){
heap[sz++] = x;
int idx = sz;
while (idx != -1) {
int par = idx / 2;
if (heap[par] <= heap[idx])
break;
swap(heap[par], heap[idx]);
idx = par;
}
}
int top() {
return heap[1];
}
void pop() {
heap[1] = heap[sz--];
int idx = 1;
// 왼쪽 자식의 인덱스 ( = 2 * idx) 가 sz 보다 크면 idx 는 리프노드
while (2 * idx <= sz) {
int lc = 2 * idx, rc = 2 * idx + 1; // 왼쪽자식, 오른쪽자식
int min_child = lc; // 두 자식 중 작은 인덱스를 담을 예정
if (rc <= sz && heap[rc] < heap[lc])
min_child = rc;
if (heap[idx] <= heap[min_child])
break;
swap(heap[idx], heap[min_child]);
idx = min_child;
}
}
#include <bits/stdc++.h>
using namespace std;
int main(void)
{
priority_queue<int> pq; // 최대 힙
// priority_queue<int, vector<int> greater<int>> 로 선언시 최소 힙
// => greater : functional 헤더파일에 정의. 기본적으로 priority_queue 를 최대힙인데, greater<int> 를 인지로 주면
// 대소관계가 뒤집혀서 최소힙으로 쓸수있다.
pq.push(10);
pq.push(2);
pq.push(5);
pq.push(9);
cout << pq.top() << '\n';
cout << pq.size() << '\n';
if (pq.empty())
cout << "pq is empty \n";
else
cout << "pq is not empty \n";
pq.pop(); // {2, 5};
cout << pq.top();
}
그러나 시간복잡도는 동일해도, priority_queue 가 2~4배 가량 더 동작속도가 빠르다.
따라서 priority_queue 에서만 제공되는 기능을 사용해도 되는 상황일 경우에는 priority_queue 를 사용하자!
조건을 만족하는 최소/최댓값을 구하는 문제(최적화 문제) 를 결정문제로 변환해 이분탐색을 수행하는 방법