기본적으로 트리의 구조. 특별히 추가되는 규칙이 있음
최대 이진 힙 MaxBinaryHeap → 부모 노드는 항상 자식 노드보다 크다.
최소 이진 힙 MinBinaryHeap → 부모 노드는 항상 자식 노드보다 작다.
삽입 - log(n)
삭제 - log(n)
탐색 - O(n)
널리 잘 알려진 자료구조 중 최소 힙이 있다. 최소 힙을 이용하여 다음과 같은 연산을 지원하는 프로그램을 작성하시오.
배열에 자연수 x를 넣는다.
배열에서 가장 작은 값을 출력하고, 그 값을 배열에서 제거한다.
프로그램은 처음에 비어있는 배열에서 시작하게 된다.
첫째 줄에 연산의 개수 N(1 ≤ N ≤ 100,000)이 주어진다. 다음 N개의 줄에는 연산에 대한 정보를 나타내는 정수 x가 주어진다. 만약 x가 자연수라면 배열에 x라는 값을 넣는(추가하는) 연산이고, x가 0이라면 배열에서 가장 작은 값을 출력하고 그 값을 배열에서 제거하는 경우이다. x는 231보다 작은 자연수 또는 0이고, 음의 정수는 입력으로 주어지지 않는다.
입력에서 0이 주어진 횟수만큼 답을 출력한다. 만약 배열이 비어 있는 경우인데 가장 작은 값을 출력하라고 한 경우에는 0을 출력하면 된다.
9
0
12345678
1
2
0
0
0
0
32
0
1
2
12345678
0
- 값을 입력하는 insert 메소드를 작성한다.
- 알맞은 자리로 갈 때까지 ‘버블업’한다.
- 프로퍼티 - 1의 값을 갖는 index 라는 변수를 만든다.
- (index-1) /2 를 내림한 parentIndex 변수를 만든다.
- parentIndex(부모 인덱스 자리에 있는 값)가 자식 인덱스보다 크다면,
- 둘을 스왑한다.
- 스왑 후 인덱스를 부모 인덱스로 다시 설정해준다.
코드로 보자면 다음과 같다.
insert(element) {
this.values.push(element);
this.bubbleUp();
}
bubbleUp() {
let idx = this.values.length - 1;
const element = this.values[idx];
while (idx > 0) {
let parentIdx = Math.floor((idx - 1) / 2);
let parent = this.values[parentIdx];
if (element >= parent) break;
this.values[parentIdx] = element;
this.values[idx] = parent;
idx = parentIdx;
}
}
참고로 this.values
는 빈 배열로 시작한다.
- 제일 앞에 있는 값을 가장 뒤에 있는 값과 바꾼다.
- 제일 뒤로 간 값을 제거한다. (pop)
- 새로운 루트를 ‘sink down’ 하여 알맞은 위치로 간다.
- 기본적으로 루트 자리인 인덱스 0에서 시작한다.
- 왼쪽 자식의 인덱스를 찾는다 → 2* 인덱스 + 1 (범위를 벗어나지 않도록 해줘야한다)
- 오른쪽 자식의 인덱스를 찾는다 → 2* 인덱스 + 2 (범위를 벗어나지 않도록 해줘야한다)
- 왼쪽 혹은 오른쪽의 자식이 작다면 스왑한다. 둘 다 작다면 둘 중 더 작은 자식과 스왑한다.
- 바꾼 자식 인덱스는 새로운 부모 인덱스가 된다.
- 어느 자식도 해당 요소보다 작지 않을때까지 스왑해준다.
- pop 한 맨 처음 루트를 반환한다.
sinkDown
을 하기 전에 배열에 값이 들어있어야하니, values의 길이가 0을 초과할 때 아까 pop 한 맨 끝값을 부모인 0번째 인덱스로 넣어두고, 본래의 위치에 갈 수 있도록 sinkDown
해준다. extractMin() {
const min = this.values[0];
const end = this.values.pop();
if (this.values.length > 0) {
this.values[0] = end;
this.sinkDown();
}
return min;
}
그렇다면 이제 의사코드로 작성했던 sinkDown이 어떻게 코드로 구현할 수 있는지 살펴보자.
sinkDown() {
let idx = 0;
const length = this.values.length;
const element = this.values[0];
while (true) {
let leftChildIdx = 2 * idx + 1;
let rightChildIdx = 2 * idx + 2;
let leftChild, rightChild;
let swap = null;
if (leftChildIdx < length) {
leftChild = this.values[leftChildIdx];
if (leftChild < element) {
swap = leftChildIdx;
}
}
if (rightChildIdx < length) {
rightChild = this.values[rightChildIdx];
if (
(swap === null && rightChild < element) ||
(swap !== null && rightChild < leftChild)
) {
swap = rightChildIdx;
}
}
if (swap === null) break;
this.values[idx] = this.values[swap];
this.values[swap] = element;
idx = swap;
}
}
sinkDown
에서도 중요한 부분은 왼쪽과 오른쪽 자식 인덱스를 구하는 부분으로 보인다.let leftChildIdx = 2 * idx + 1;
let rightChildIdx = 2 * idx + 2;
이제 힙을 구현했으니 문제인 최소 힙에 적용해보자.
const path = process.platform === "linux" ? "/dev/stdin" : "input.txt";
const fs = require("fs");
const [N, ...arr] = fs
.readFileSync(path)
.toString()
.trim()
.split(`\n`)
.map(Number);
class MinBinaryHeap {
constructor() {
this.values = [];
}
insert(element) {
this.values.push(element);
this.bubbleUp();
}
bubbleUp() {
let idx = this.values.length - 1;
const element = this.values[idx];
while (idx > 0) {
let parentIdx = Math.floor((idx - 1) / 2);
let parent = this.values[parentIdx];
if (element >= parent) break;
this.values[parentIdx] = element;
this.values[idx] = parent;
idx = parentIdx;
}
}
extractMin() {
const min = this.values[0];
const end = this.values.pop();
if (this.values.length > 0) {
this.values[0] = end;
this.sinkDown();
}
return min;
}
sinkDown() {
let idx = 0;
const length = this.values.length;
const element = this.values[0];
while (true) {
let leftChildIdx = 2 * idx + 1;
let rightChildIdx = 2 * idx + 2;
let leftChild, rightChild;
let swap = null;
if (leftChildIdx < length) {
leftChild = this.values[leftChildIdx];
if (leftChild < element) {
swap = leftChildIdx;
}
}
if (rightChildIdx < length) {
rightChild = this.values[rightChildIdx];
if (
(swap === null && rightChild < element) ||
(swap !== null && rightChild < leftChild)
) {
swap = rightChildIdx;
}
}
if (swap === null) break;
this.values[idx] = this.values[swap];
this.values[swap] = element;
idx = swap;
}
}
}
let heap = new MinBinaryHeap();
let result = "";
for (let i = 0; i < N; i++) {
if (arr[i] === 0) {
result += (heap.extractMin() || 0) + "\n";
} else {
heap.insert(arr[i]);
}
}
console.log(result.trim());
//시간초과
for (let i = 0; i < N; i++) {
if (arr[i] === 0) {
if (heap.values.length > 0) {
console.log(heap.extractMin());
} else console.log(0);
} else {
heap.insert(arr[i]);
}
}
출처