우선순위가 높은 데이터가 먼저 나가는 형태의 자료구조이다. 데이터를 우선순위에 따라 처리하고 싶을 때 사용한다.
JavaScript에는 우선순위 큐 자료구조가 구현되어 있지 않다. 배열, 연결리스트, 힙 기반으로 우선순위 큐 자료구조를 구현 할 수 있으나, 배열이나 연결리스트 기반으로 구현 시 시간복잡도가 증가하기 때문에 보통 힙을 구현하고 힙을 기반으로 우선순위 큐를 구현한다.
배열 기반 우선순위 큐
삽입 | 삭제 |
---|---|
연결리스트 기반 우선순위 큐
삽입 | 삭제 |
---|---|
힙 기반 우선순위 큐
삽입 | 삭제 |
---|---|
배열과 연결리스트는 데이터 삽입시 모든 인덱스를 탐색해야하는 경우 시간복잡도가 으로 성능이 좋지 않을 수 있다.
힙은 트리 기반의 자료구조이다. 규칙에 따라 크게 Max Heap
과 Min Heap
으로 나뉜다.
Max Heap (최대 힙)
- 루트 노드가 가장 큰 값을 가지며 우선적으로 제거된다.
- 부모 노드의 값 자식 노드의 값
Min Heap (최소 힙)
- 루트 노드가 가장 작은 값을 가지며 우선적으로 제거된다.
- 자식 노드의 값 부모 노드의 값
힙 자료구조는 완전이진트리로 구현한다. 완전이진트리는 각각의 부모노드는 두개의 자식 노드(Left, Right)만 가질 수 있고, 트리의 가장 아래 층을 제외하고는 상단의 모든 레벨이 채워져야 한다.
왼쪽 자식노드의 인덱스
: 부모 노드의 인덱스 x 2 + 1
오른쪽 자식노드의 인덱스
: 부모 노드의 인덱스 x 2 + 2
부모 노드의 인덱스
: (자식 노드의 인덱스 - 1) / 2 (소수점 버림)
1. 부모노드는 항상 자식 노드보다 값이 작아야(커야) 한다.
2. 한 레벨이 모두 채워져야 다음 레벨로 트리가 늘어날 수 있다.
이 두 가지 규칙을 지키는 자료구조를 힙(Heap)이라고 할 수 있다.
힙은 주로 최소(최대)값을 의 시간 복잡도로 얻어내기 위해서 사용된다.
배열이나 연결 리스트 같은 자료구조는 최소(최대)값을 얻기 위해 이 걸린다.
힙의 경우 최상위 노드에 항상 최소(최대)값이 담겨있기 때문에 최상위 노드의 조회 의 시간 복잡도만으로 최소(최대)값을 얻을 수 있다.
class MinHeap {
constructor() {
this.heap = [];
}
// 인덱스 값 가져오기
getLeftChildIndex(parentIndex) {
return 2 * parentIndex + 1;
}
getRightChildIndex(parentIndex) {
return 2 * parentIndex + 2;
}
getParentIndex(childIndex) {
return Math.floor((childIndex - 1) / 2);
}
// 자식/부모 노드 존재하는지 체크
hasLeftChild(index) {
return this.getLeftChildIndex(index) < this.heap.length;
}
hasRightChild(index) {
return this.getRightChildIndex(index) < this.heap.length;
}
hasParent(index) {
return this.getParentIndex(index) >= 0;
}
// 자식/부모 노드 값 가져오기
leftChild(index) {
return this.heap[this.getLeftChildIndex(index)];
}
rightChild(index) {
return this.heap[this.getRightChildIndex(index)];
}
parent(index) {
return this.heap[this.getParentIndex(index)];
}
// 최상위 노드 peek
peek() {
if (this.heap.length === 0) {
return null;
}
return this.heap[0];
}
}
부모/자식 노드의 인덱스 값 반환, 존재 여부 체크, 노드 값 반환을 하는 메서드를 구현한다.
peek()
은 항상 최상위 노드를 반환한다.
class MinHeap {
...
insert(data) {
this.heap.push(data);
this.heapifyUp();
}
heapifyUp() {
let index = this.heap.length - 1;
while (this.hasParent(index) && this.parent(index) > this.heap[index]) {
const parentIndex = this.getParentIndex(index);
this.swap(parentIndex, index);
index = parentIndex;
}
swap(index1, index2) {
[this.heap[index1], this.heap[index2]] = [this.heap[index2], this.heap[index1]];
}
}
insert()
heapifyup()
변수 index
에 해당하는 노드가
를 만족할 경우, 부모 인덱스와 자식 노드의 인덱스를 교환한다.
변수 index
에 부모 노드의 인덱스 값을 할당하고 While 문이 반복된다.
이를 통해 최신 노드가 제 자리를 찾아 갈 수 있도록 아래에서 부터 위로 끌어 올린다.
class MinHeap {
...
remove() {
if (this.heap.length === 0) {
return null;
}
const root = this.heap[0]; // 최상위 노드
const last = this.heap.pop(); // 끝에 있는 노드를 꺼내어 last에 할당
if (this.heap.length > 0) {
this.heap[0] = last;
this.heapifyDown();
}
return root;
}
heapifyDown() {
let index = 0;
while (this.hasLeftChild(index)) {
let smallerChildIndex = this.getLeftChildIndex(index);
if (this.hasRightChild(index) && this.rightChild(index) < this.leftChild(index)) {
smallerChildIndex = this.getRightChildIndex(index);
}
if (this.heap[index] < this.heap[smallerChildIndex]) {
break;
} else {
this.swap(index, smallerChildIndex);
}
index = smallerChildIndex;
}
}
}
insert()
root
와 last
에 각각 최상위 노드와 제일 끝 노드를 할당heapifyDown()
최상위 노드 ( insert()
에서 최상위 노드가 제일 끝 노드)를 시작으로, 두 자식노드 중 값이 작은 노드를 찾아, 부모 노드와 비교하여 자식 노드의 값이 더 작을경우 그 둘의 인덱스 값을 교환한다. 변수 index
에 자식 노드의 값을 할당하고 while 문이 반복된다.
이를 통해, 최근 자리가 바뀐 최상위 노드의 올바른 위치를 찾아 아래로 끌어 내린다.
const heap = new MinHeap();
heap.insert(2);
heap.insert(8);
heap.insert(4);
heap.insert(6);
console.log(heap.peek()); // 2
console.log(heap.remove()); // 2
console.log(heap.peek()); // 4
console.log(heap.remove()); // 4
heap.insert(2);
connsole.log(heap.peek()) // 2
이런 유용한 정보를 나눠주셔서 감사합니다.