队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
队列介绍
队列遵循FIFO(First In First Out,先进先出)原则的一组有序的项。
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
队列有基本队列,还有其他修改版本的队列,比如:优先队列、循环队列。
基本队列
基本队列是队列的基本存储结构,它是运算受限制的基本表(线性表)。建立基本队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置,如图所示。
20190815132004421.png
- 初始化:数组的front和rear都指向0。
- 入队:队不满,数组不越界,先队尾位置传值,再队尾下标+1。
- 出队:队不空,先取队头位置元素,在队头+1。
基本队列的实现
创建队列后需要为其定义一些方法,一般来说队列包含以下方法:
- enqueue(element):向队的尾部添加一个新的项
- dequeue():移除队列第一项,并返回被移除的元素
- front():返回队列第一项,队列不做任何变动
- isEmpty():如果队列中没有任何元素返回true,否则返回false
- size():返回队列包含的元素个数
具体实现:
function Queue() {
let 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.toString())
}
}
var queue = new Queue();
queue.enqueue('hello');
queue.enqueue('world');
queue.enqueue('php');
queue.enqueue('javascript');
queue.enqueue('node.js');
console.log(queue.isEmpty()); // false
console.log(queue.print()); //hello,world,php,javascript,node.js
console.log(queue.size()); //5
console.log(queue.front()); //hello
es6实现Queue
用es6的class语法实现Queue类,用WeakMap保存私用属性items,并用闭包返回Queue类,来看具体实现:
let Queue = (function () {
let items = new WeakMap
class Queue {
constructor () {
items.set(this, [])
}
enqueue (element) {
let q = items.get(this)
q.push(element)
}
dequeue () {
let q = items.get(this)
return q.shift()
}
front () {
let q = items.get(this)
return q[0]
}
isEmpty () {
let q = items.get(this)
return q.length === 0
}
size () {
let q = items.get(this)
return q.length
}
print () {
let q = items.get(this)
console.log(q.toString())
}
}
return Queue
})()
不足:每个空间域只能利用一次。造成空间浪费。并且非常容易越界!
优先队列
优先队列是基本队列的修改版本,元素的添加和移除是基于优先级的。一个现实例子是,在银行排队办业务的顺序。VIP客户的优先级要高于普通客户的。另一个例子是医院的急诊科候诊室。医生会优先处理病情比较严重的患者。通常,护士会鉴别分类,根据患者病情的严重程度放号。
实现一个优先队列,有两种选项:
- 设置优先级,然后在正确的位置添加元素。(优先添加,正常出队)
- 用入列操作添加元素,然后按照优先级移除它们。(正常添加,优先出队)
function PriorityQueue() {
var items = [];
//需要插入队列的元素(该元素为对象,包括值和优先级)
function QueueElement(element, priority) {
this.element = element;
this.priority = priority;
}
//插入元素到队列中的方法
this.enqueue = function (element, priority) {
//需要插入队列的元素
var queueElement = new QueueElement(element, priority);
if(this.isEmpty()) {
//当队列为空时,直接往队列中添加元素
items.push(queueElement);
}else{
//当队列不为空时,遍历队列中的元素,当需要添加的元素的优先级小于(队列中)当前元素的优先级,就把该元素插入到当前元素之前
var added = false;
for(var 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);
}
}
}
//查看队列是否为空,如果为空,返回true;否则返回false
this.isEmpty = function() {
return items.length == 0;
}
//查看队列
this.print = function() {
return items;
}
}
var priorityQueue = new PriorityQueue();
priorityQueue.enqueue('dee', 10);
priorityQueue.enqueue('Learning', 2);
priorityQueue.enqueue('JavaScript', 8);
priorityQueue.enqueue('Algorithms', 20);
priorityQueue.enqueue('Data Structures', 20);
console.log(priorityQueue.print());
入队时如果队列为空直接加入队列,否则进行比较,priority小的优先级高,优先级越高放在队列的越前面.
循环队列
针对上述的问题。循环队列可以有效的内存重复利用。
数组实现的循环队列就是在逻辑上稍作修改。我们假设(约定)数组的最后一位的下一个index是首位。因为我们队列中只需要front和tail两个指标。不需要数组的实际地址位置相关数据。和它无关。所以我们就只需要考虑尾部的特殊操作即可。
- 初始化:数组的front和rear都指向0.
- 入队:队不满,先队尾位置传值,再rear=(rear + 1) % maxsize;
- 出队:队不空,先取队头位置元素,front=(front + 1)%maxsize;
- 是否为空:return rear == front;
- 大小:return (rear+maxsize-front)%maxsize;
-
有几点需要注意的,指标相加如果遇到最后需要转到头的话。可以判断是否到数组末尾位置。也可以直接+1求余。其中maxsize是数组实际大小。
20190816002656435.png
循环队列的一个例子就是击鼓传花的游戏。在这个游戏中,孩子们围成一个圆圈,把花尽快的传递给旁边的人。某一时刻传花停止,这个时候花在谁手里,谁就退出圆圈结束游戏。重复这个过程,直到只剩一个孩子(胜者)。
另一个类似的案例是,约瑟夫环问题。
//基本队列Queue同上
//循环队列
//@param Obj nameList 名单
//@param Int num 指定的传递次数
function hotPotato(nameList, num) {
var queue = new Queue();
//把名单插入队列
for(var i = 0; i < nameList.length; i++) {
queue.enqueue(nameList[i]);
}
//淘汰者的名字初始值
var eliminated = '';
//当队列里的人数大于1人时,继续传递
while(queue.size() > 1) {
for(var i = 0; i < num; i++) {
//每次把队列头部弹出的队员再次插入队列的尾部,行程一个循环队列
queue.enqueue(queue.dequeue());
}
//当循环停止时,即到了指定的传递次数时,弹出队列头部的队员
eliminated = queue.dequeue();
console.log(eliminated + '被淘汰');
}
//当队列中只剩下一个队员时,即是胜利者
return queue.dequeue();
}
var names = ['dee', 'death mask', 'saga', 'mu', 'alexis'];
var winner = hotPotato(names, 7);
console.log('胜利者是' + winner);
小结
这篇文章主要对队列做了简单介绍,对队列以及相关应用做了简单实现。如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞。
朵拉姐【ITI2018】的前端摸鱼技术群
欢迎大家技术交流 内推 摸鱼 求助皆可 - 链接
系列链接(后续都会更新完毕)
-
JS数组不是“真”的数组?https://blog.csdn.net/weistar103/article/details/120429675
-
一图看懂归并排序https://blog.csdn.net/weistar103/article/details/120347948
-
终于理解希尔排序与插入排序https://blog.csdn.net/weistar103/article/details/120260022
-
一图读懂快速排序(Quick Sort)https://blog.csdn.net/weistar103/article/details/120150413
-
一图读懂插入排序(Insertion Sort)https://blog.csdn.net/weistar103/article/details/120150413
-
终于理解冒泡排序与选择排序的区别https://blog.csdn.net/weistar103/article/details/120065258