队列,是先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作。
简而言之队列就像日常生活的排队买东西先到的先享受服务。
队列的实现
普通的队列常用的有以下几个方法:
enqueue 向队列尾部添加一个(或多个)新的项
dequeue 移除队列的第一(即排在队列最前面的)项,并返回被移除的元素
head 返回队列第一个元素,队列不做任何变动
tail 返回队列最后一个元素,队列不做任何变动
isEmpty 队列内无元素返回true,否则返回false
size 返回队列内元素个数
clear 清空队列
class Queue {
constructor() {
this._items = [];
}
enqueue(item) {
this._items.push(item);
}
dequeue() {
return this._items.shift();
}
head() {
return this._items[0];
}
tail() {
return this._items[this._items.length - 1];
}
isEmpty() {
return !this._items.length;
}
size() {
return this._items.length;
}
clear() {
this._items = [];
}
}
同样地,参照上一篇栈的实现方法,我们也可以优化队列的代码:
let Queue = (function () {
const item = new WeakMap();
class Queue {
constructor () {
item.set(this,[])
}
enqueue(element) {
let q = item.get(this);
q.push(element);
}
dequeue() {
let q = item.get(this);
let r = q.shift();
return r;
}
//其他方法
}
return Queue
})()
这样外界就不能访问私有属性item,确保只能使用我们提供的api来操作队列
优先队列
队列大量应用在计算机科学以及我们的生活中,我们在之前话题中实现的默认队列也有一些修改版本。其中一个修改版就是优先队列。元素的添加和移除是基于优先级的。一个现实中的例子是医院的(急诊科)候诊室。医生会优先处理病情比较严重的患者。通常,护士会鉴别分类,根据患者病情的严重程度放号。
function PriorityQueue() {
let items = [];
function QueueElement (element, priority){ // {1}
this.element = element;
this.priority = priority;
}
this.enqueue = function(element, priority){
let queueElement = new QueueElement(element, priority);
let added = false;
for (let i=0; i<items.length; i++){
if (queueElement.priority < items[i].priority){ // {2}
items.splice(i,0,queueElement); // {3}
added = true;
break; // {4}
}
}
if (!added){
items.push(queueElement); //{5}
}
};
this.print = function(){
for (let i=0; i<items.length; i++){
console.log(`${items[i].element} -${items[i].priority}`);
}
};
//其他方法和默认的Queue实现相同
}
循环队列
另一个修改版的队列实现,就是循环队列。循环队列的一个例子就是击鼓传花游戏( Hot
Potato)。在这个游戏中,孩子们围成一个圆圈,把花尽快地传递给旁边的人。某一时刻传花停止,这个时候花在谁手里,谁就退出圆圈结束游戏。重复这个过程,直到只剩一个孩子(胜者)。
function hotPotato(nameList, num) {
let queue = new Queue(); // {1}
for (let i = 0; i < nameList.length; i++) {
queue.enqueue(nameList[i]); // {2}
}
let eliminated = '';
while (queue.size() > 1) {
for (let i = 0; i < num; i++) {
queue.enqueue(queue.dequeue()); // {3}
}
eliminated = queue.dequeue(); // {4}
console.log(eliminated + ' 在击鼓传花游戏中被淘汰.');
}
return queue.dequeue(); // {5}
}
const arr = ["小刘","小王","小李","bob","lilly","cindy"];
console.log("最终胜利者是:"+hotPotato(arr,3))
打印结果为:
bob 在击鼓传花游戏中被淘汰.
小王 在击鼓传花游戏中被淘汰.
小刘 在击鼓传花游戏中被淘汰.
小李 在击鼓传花游戏中被淘汰.
cindy 在击鼓传花游戏中被淘汰.
最终胜利者是:lilly
循环队列还有一个变式应用——约瑟夫环:
有一个数组a[100]存放0~99;要求每隔两个数删掉一个数,到末尾时循环至开头继续进行,求最后一个被删掉的数。
const arr_100 = Array.from({ length: 100 },(item,index)=>index)
// 创建一个长度为100的数组
function delRing(list) {
const queue = new Queue();
list.forEach(e => { queue.enqueue(e); });
let index = 0;
while (queue.size() !== 1) {
const item = queue.dequeue();
index += 1;
if (index % 3 !== 0) {
queue.enqueue(item);
}
}
return queue.tail();
}
console.log(delRing(arr_100));
队列还可以用来生成斐波那契数列
function fibonacci(n) {
const queue = new Queue();
queue.enqueue(1);
queue.enqueue(1);
let index = 0;
while(index < n - 2) {
index += 1;
// 出队列一个元素
const delItem = queue.dequeue();
// 获取头部值
const headItem = queue.head();
const nextItem = delItem + headItem;
queue.enqueue(nextItem);
}
return queue.tail();
}
console.log(fibonacci(9)); // 34