Github | javascript-algorithms | trekhleb
changePriority
등의 메서드는 위 링크 참조
export default class PriorityQueue {
constructor() {
if (new.target === Heap) {
throw new TypeError('Cannot construct Heap instance directly');
}
// 힙을 배열로 표현함
this.heapContainer = [];
this.priorities = new Map();
}
// 왼쪽 자식의 인덱스 = 부모의 인덱스 *2 + 1
getLeftChildIndex(parentIndex) {
return (2 * parentIndex) + 1;
}
// 오른쪽 자식의 인덱스 = 부모의 인덱스 *2 + 2
getRightChildIndex(parentIndex) {
return (2 * parentIndex) + 2;
}
// 부모의 인덱스 = ((자식의 인덱스 -1) / 2 ))의 내림값
getParentIndex(childIndex) {
return Math.floor((childIndex - 1) / 2);
}
hasParent(childIndex) {
return this.getParentIndex(childIndex) >= 0;
}
hasLeftChild(parentIndex) {
return this.getLeftChildIndex(parentIndex) < this.heapContainer.length;
}
hasRightChild(parentIndex) {
return this.getRightChildIndex(parentIndex) < this.heapContainer.length;
}
leftChild(parentIndex) {
return this.heapContainer[this.getLeftChildIndex(parentIndex)];
}
rightChild(parentIndex) {
return this.heapContainer[this.getRightChildIndex(parentIndex)];
}
parent(childIndex) {
return this.heapContainer[this.getParentIndex(childIndex)];
}
swap(indexOne, indexTwo) {
const tmp = this.heapContainer[indexTwo];
this.heapContainer[indexTwo] = this.heapContainer[indexOne];
this.heapContainer[indexOne] = tmp;
}
peek() {
if (this.heapContainer.length === 0) {
return null;
}
return this.heapContainer[0];
}
poll() {
if (this.heapContainer.length === 0) {
return null;
}
if (this.heapContainer.length === 1) {
return this.heapContainer.pop();
}
const item = this.heapContainer[0];
// 마지막 요소를 끝에서 맨 위로 올림
this.heapContainer[0] = this.heapContainer.pop();
this.heapifyDown();
return item;
}
add(item, priority = 0) {
this.priorities.set(item, priority);
this.heapContainer.push(item);
this.heapifyUp();
return this;
}
remove(item) {
// 삭제해야할 아이템의 갯수를 파악
const numberOfItemsToRemove = this.find(item).length;
for (let iteration = 0; iteration < numberOfItemsToRemove; iteration += 1) {
// heapify 프로세스 후 인덱스가 변경되기 때문에
// 삭제 후 매번 삭제할 요소의 인덱스를 찾아야함
const indexToRemove = this.find(item).pop();
// 마지막 요소를 삭제해야할 경우 heapify가 필요하지 않음
if (indexToRemove === (this.heapContainer.length - 1)) {
this.heapContainer.pop();
} else {
// Move last element in heap to the vacant (removed) position.
// 마지막 요소를 삭제하는 것이 아닌 경우
// 삭제하려는 요소의 자리에 마지막 요소의 값을 할당함
this.heapContainer[indexToRemove] = this.heapContainer.pop();
// 부모 요소를 찾음
const parentItem = this.parent(indexToRemove);
// Max heap의 경우
// 부모 요소가 없거나 부모 요소가 현재 요소값보다 크면 heapify down
// 그 외의 경우 heapify up
if (
this.hasLeftChild(indexToRemove)
&& (
!parentItem
|| this.pairIsInCorrectOrder(parentItem, this.heapContainer[indexToRemove])
)
) {
this.heapifyDown(indexToRemove);
} else {
this.heapifyUp(indexToRemove);
}
}
}
this.priorities.delete(item);
return this;
}
// 기존 Min heap에서는 같은 값을 가진 index를 찾아 배열로 반환했지만,
// 우선 순위 큐에서는 priority가 같은 경우를 찾아 배열로 반환함
find(item) {
const foundItemIndices = [];
for (let itemIndex = 0; itemIndex < this.heapContainer.length; itemIndex += 1) {
if (this.priorities.get(item) === this.priorities.get(this.heapContainer[itemIndex])) {
foundItemIndices.push(itemIndex);
}
}
return foundItemIndices;
}
isEmpty() {
return !this.heapContainer.length;
}
toString() {
return this.heapContainer.toString();
}
heapifyUp(customStartIndex) {
// 힙 컨테이너의 마지막 요소를
// 상위 요소에 대해 올바른 순서가 될 때까지 위로 올림
let currentIndex = customStartIndex || this.heapContainer.length - 1;
// 부모 요소가 존재하고
// && 부모 요소의 값과 현재 값을 비교해서 바꿔야 하는 상황이면 Swap
// Swap을 했을 경우 현재 인덱스를 부모 인덱스로 업데이트
while (
this.hasParent(currentIndex)
&& !this.pairIsInCorrectOrder(
this.parent(currentIndex), this.heapContainer[currentIndex]
)
) {
this.swap(currentIndex, this.getParentIndex(currentIndex));
currentIndex = this.getParentIndex(currentIndex);
}
}
heapifyDown(customStartIndex = 0) {
// head부터 시작해서 부모 요소와 자식 요소를 비교 후
// 올바른 순서가 될 때까지 아래로 내림
let currentIndex = customStartIndex;
let nextIndex = null;
while (this.hasLeftChild(currentIndex)) {
if (
this.hasRightChild(currentIndex)
&& this.pairIsInCorrectOrder(
this.rightChild(currentIndex), this.leftChild(currentIndex)
)
) {
// Max heap의 경우
// 왼쪽 자식과 오른쪽 자식 모두 있고
// 오른쪽 자식이 왼쪽 자식보다 클 경우
// 다음 인덱스는 오른쪽 자식으로 설정
nextIndex = this.getRightChildIndex(currentIndex);
} else {
// 왼쪽 자식이 있는 상황에서
// 오른쪽 자식이 없거나
// 오른쪽 자식이 있어도, 오른쪽 자식이 왼쪽 자식보다 작거나 같을 경우
// 다음 인덱스는 왼쪽 자식으로 설정
nextIndex = this.getLeftChildIndex(currentIndex);
}
// Max heap의 경우
// 현재 인덱스의 값이 다음 인덱스의 값보다 클 경우 => break
if (this.pairIsInCorrectOrder(
this.heapContainer[currentIndex],
this.heapContainer[nextIndex],
)) {
break;
}
// Max heap의 경우
// 현재 인덱스의 값이 다음 인덱스의 값보다 작거나 같을 경우 => swap
this.swap(currentIndex, nextIndex);
currentIndex = nextIndex;
}
}
pairIsInCorrectOrder(firstElement, secondElement) {
// Min Heap일 경우
// 첫번째 인자가 두번째 인자보다 크거나 같아야함.
// 그 반대의 경우 incorrect = true
return firstElement < secondElement
}
}
Github | tech-interview-for-developer
Github | Interview_Question_for_Beginner
Github | javascript-algorithms | trekhleb
Photo by Alain Pham on Unsplash