이 내용은 'Learning JavaScript Data Structures and Algorithms'(로이아니 그로네르 저, 이일웅 역) 책의 내용을 제 생각과 함께 정리한 글입니다.
틀린 내용 혹은 수정이 필요한 내용이 있다면 말씀해주시면 감사하겠습니다.
큐는 FIFO(First In First Out = 선입선출) 원리에 따라 정렬된 컬렉션이다.
새 원소는 뒤로 들어가서 앞에서 빠져나가는 구조다.
따라서 마지막에 추가된 원소는 큐의 뒷부분에서 제일 오래 기다려야 한다.
줄서기를 떠올려보자. 영화관에서 줄을 서 있을 때 맨 앞의 사람이 가장 먼저 표를 사는 것은 당연한 일이다.
큐에서 사용할 메소드는 다음과 같다.
enqueue(element)
: 큐의 뒤쪽에 원소(들)를 추가한다.
dequeue()
: 큐의 첫 번째 원소(큐의 맨 앞쪽에 위치한 원소)를 반환하고 큐에서 삭제한다.
front()
: 큐의 첫 번째 원소를 반환하되 큐 자체는 그대로 놔둔다. (peek()
과 비슷)
isEmpty()
: 큐가 비어있으면 true
, 아니면 false
를 반환한다.
size()
: 큐의 원소 개수를 반환한다. 배열의 length
와 같다.
function Queue() {
var items = [];
this.enqueue = function (element) {
items.push(element);
};
this.dequeue = function () {
return items.shift();
};
this.front = function () {
return items[0];
};
this.isEmpty = function () {
return items.length === 0;
};
this.size = function () {
return items.length;
};
this.print = function () {
console.log(items);
};
}
Queue와 Stack 클래스는 매우 유사하다. 적용하는 원리가 FIFO, LIFO로 다르기 때문에
dequeue
와 마찬가지로front
메소드만 다르다.
const queue = new Queue();
console.log(queue.isEmpty()); // true
queue.enqueue('John');
queue.enqueue('Jack');
queue.enqueue('Camilla');
queue.print(); // [ 'John', 'Jack', 'Camilla' ]
console.log(queue.size()); // 3
console.log(queue.isEmpty()); // false
queue.dequeue();
queue.dequeue();
queue.print(); // [ 'Camilla' ]
비행기 탑승을 떠올려보자. 1등석과 비즈니스석 승객은 항상 이코노미석 승객보다 우선순위가 높다.
function PrirorityQueue() {
let items = [];
function QueueElement(element, priority) {
this.element = element;
this.priority = priority; // 우선순위(priority)가 추가됨
}
this.enqueue = function (element, priority) {
let queueElement = new QueueElement(element, priority);
if (this.isEmpty()) {
items.push(queueElement); // 큐가 비어있다면 원소를 그냥 넣는다.
}
// general case
else {
let added = false;
for (let i = 0; i < items.length; i++) {
// 만약 우선순위가 더 높은 원소가 들어왔다면
if (queueElement.priority < items[i].priority) {
// 해당 위치에 원소를 추가한다.
items.splice(i, 0, queueElement);
added = true;
break; // 루프문 종료
}
}
// 만약 새 원소의 우선순위가 가장 낮다면
if (!added) {
items.push(queueElement);
}
}
};
this.isEmpty = function () {
return items.length === 0;
};
this.print = function () {
console.log(items);
};
}
const priorityQueue = new PrirorityQueue();
priorityQueue.enqueue('John', 2);
priorityQueue.enqueue('Jack', 1);
priorityQueue.enqueue('Camilla', 3);
priorityQueue.print();
// 실행결과: [ QueueElement { element: 'Jack', priority: 1 },
// QueueElement { element: 'John', priority: 2 },
// QueueElement { element: 'Camilla', priority: 3 } ]
이러한 로직으로 구현한 우선순위 큐를 최소 우선순위 큐(min priority queue)라고 부르는데, 우선순위 값이 낮으면 낮을수록(1이 가장 높은 우선순위다) 앞자리로 이동하기 때문이다.
반대로 우선순위 값이 크면 클수록 앞자리로 보내는 최대 우선순위 큐(max priority queue)도 있다.
뜨거운 감자(Hot Potato)게임 은 환형 큐(Circular Queue)의 대표적인 예다.
원을 그리고 서 있는 아이들이 뜨거운 감자를 옆 사람에게 최대한 빨리 전달하다가, 갑자기 모두 동작을 멈추고 그 때 뜨거운 감자를 손에 들고 있는 아이를 벌칙으로 퇴장시키는 게임이다. 최후의 1인(승자)이 남을 때까지 게임은 계속된다.
이를 코드로 구현해보자.
// Circular Queue (Hot Potato Game)
// nameList: 게임에 참여한 사람들의 이름, num: 큐를 순회할 횟수
function hotPotato(nameList, num) {
let queue = new Queue();
for (let i = 0; i < nameList.length; i++) queue.enqueue(nameList[i]);
let eliminated = '';
// 최후의 1인이 남을 때까지
while (queue.size() > 1) {
for (let i = 0; i < num; i++) queue.enqueue(queue.dequeue()); // 맨 앞의 원소를 꺼내어 맨 뒤로 넣는다.
eliminated = queue.dequeue(); // num만큼 반복이 끝난 후 뜨거운 감자를 들고 있던 사람을 퇴장시킨다.
console.log(`${eliminated}을(를) 게임에서 퇴장시킵니다.`);
}
return queue.front(); // 마지막 사람이 승자가 된다.
}
const names = ['John', 'Jack', 'Camilla', 'Ingrid', 'Carl'];
const winner = hotPotato(names, 7);
console.log(`승자는 ${winner} 입니다.`);
// 결과
// Camilla을(를) 게임에서 퇴장시킵니다.
// Jack을(를) 게임에서 퇴장시킵니다.
// Carl을(를) 게임에서 퇴장시킵니다.
// Ingrid을(를) 게임에서 퇴장시킵니다.
// 승자는 John 입니다.
hotPotato
함수의 num
인자를 바꾸면 다른 결과를 반환할 것이다.