18주차 · 힙과 우선순위 큐
보통자료구조

최소 힙 직접 구현

연산 목록 operations가 주어져요. 최소 힙을 직접 만들어 순서대로 처리하고, 값을 꺼내는 연산의 결과만 모아 배열로 반환해요.

연산은 세 가지예요. ["push", x]는 x를 넣고 아무것도 반환하지 않아요. ["pop"]은 가장 작은 값을 꺼내 반환해요. ["peek"]은 가장 작은 값을 꺼내지 않고 확인만 해요. 힙이 비어 있으면 poppeek 모두 null을 반환해요.

결과 배열에는 poppeek의 반환값만 순서대로 담아요.

이 문제의 목적은 정렬 함수를 부르는 게 아니라 힙 내부 동작을 직접 만들어 보는 거예요. 배열을 완전 이진 트리로 보고, 인덱스 i의 부모가 (i-1)/2, 자식이 2i+12i+2라는 규칙 위에서 위로 올리기(sift up)와 아래로 내리기(sift down)를 구현해 보세요.

Python에는 heapq가 내장되어 있지만, 이 문제만큼은 직접 만들어 보길 권해요. 다음 문제부터는 마음껏 써요.

예시

예시 1
예시 2
예시 3

제한 사항

  • 1 ≤ operations.length ≤ 100,000
  • -10^9 ≤ x ≤ 10^9
  • 같은 값이 여러 번 들어올 수 있어요.
push는 배열 끝에 넣고 부모보다 작은 동안 부모와 맞바꾸며 올라가요. pop은 뿌리를 빼내고 마지막 원소를 뿌리에 올린 뒤, 두 자식 중 더 작은 쪽과 맞바꾸며 내려가요. 두 연산 모두 높이만큼만 움직이니까 O(log n)이에요.
javascript
class MinHeap {
/** @param {(a: any, b: any) => number} [compare] 음수면 a가 먼저 나옵니다. */
constructor(compare) {
this.items = [];
this.compare = compare ?? ((a, b) => (a < b ? -1 : a > b ? 1 : 0));
}
get size() {
return this.items.length;
}
peek() {
return this.items.length > 0 ? this.items[0] : null;
}
push(value) {
const a = this.items;
a.push(value);
let i = a.length - 1;
while (i > 0) {
const parent = (i - 1) >> 1;
if (this.compare(a[parent], a[i]) <= 0) break;
[a[parent], a[i]] = [a[i], a[parent]];
i = parent;
}
}
pop() {
const a = this.items;
if (a.length === 0) return null;
const top = a[0];
const last = a.pop();
if (a.length > 0) {
a[0] = last;
let i = 0;
for (;;) {
const l = i * 2 + 1;
const r = l + 1;
let small = i;
if (l < a.length && this.compare(a[l], a[small]) < 0) small = l;
if (r < a.length && this.compare(a[r], a[small]) < 0) small = r;
if (small === i) break;
[a[small], a[i]] = [a[i], a[small]];
i = small;
}
}
return top;
}
}
function heapOps(operations) {
const heap = new MinHeap();
const out = [];
for (const [kind, value] of operations) {
if (kind === "push") heap.push(value);
else if (kind === "pop") out.push(heap.pop());
else out.push(heap.peek());
}
return out;
}
에디터를 불러오고 있어요…
코드를 작성하고를 눌러 예시 테스트를 확인해 보세요.