1.队列基础知识
- 队列有一片连续的存储区,可以是数组,也可以是链表,可以存储任意类型的元素
- 队列有两个指针,一个头指针,一个尾指针,头指针指向第一个节点,尾指针指向最后一个节点的下一位(左闭右开,尾指针减去头指针就是元素的数量)。头部出队,尾部入队,先入先出(FIFO)
- 标准队列支持两种基本操作,出队和入队
- 队首出队,头指针向后移动一位,在逻辑层面,认为元素已出队
- 队尾入队,把数据添加进来,尾指针向后移动一位
- 允许中间入队的队列称为优先队列
- 假溢出:其实还有位置可以放入其他元素,只是因为尾指针走到了最后一位,普通队列会误以为队列满了,为了解决假溢出,提出循环队列
- 循环队列:走到尾部,再从头开始
2.几种经典的队列实现方法
(1)普通队列
// 队列类
class Queue {
constructor(n = 10) {
this.arr = new Array(n); // 一片连续的存储空间
this.head = 0; // 队首指针
this.tail = 0; // 队尾指针
}
// 尾部入队
push(x) {
if (this.fulll()) {
console.log("queue full");
return;
}
// 放入tail指向的位置,并把tail向后移动一位
this.arr[this.tail] = x;
this.tail += 1;
return;
}
// 头部出队
pop() {
// 队列为空,不操作
if (this.empty()) return;
// 头指针向后移动一位,逻辑上认为已出队
this.head += 1;
return;
}
// 判空
empty() {
return this.head === this.tail;
}
// 判满
fulll() {
return this.tail === this.arr.length;
}
// 查看队首元素
front() {
if (this.empty()) return null;
return this.arr[this.head];
}
// 队列元素数量
size() {
return this.tail - this.head;
}
output() {
let res = "";
for (let i = this.head; i < this.tail; i++) {
res += this.arr[i];
}
console.log(res);
}
}
function main() {
let arr = new Queue(5);
arr.push(1);
arr.push(2);
arr.push(5);
arr.output();
console.log(arr.size());
arr.pop();
arr.output();
}
main();
(2)循环队列
// 队列类
class Queue {
constructor(n = 10) {
this.arr = new Array(n); // 一片连续的存储空间
this.head = 0; // 队首指针
this.tail = 0; // 队尾指针
this.cnt = 0; // 当前元素数量
}
// 入队
push(x) {
if (this.fulll()) {
console.log("queue full");
return;
}
// 放入tail指向的位置,并把tail向后移动一位,元素数量加1
this.arr[this.tail] = x;
this.tail += 1;
this.cnt += 1;
// 尾指针走到最后一位,再从0开始,这里可以使用取余算法优化,后面算法题中展示
if (this.tail === this.arr.length) this.tail = 0;
return;
}
// 出队
pop() {
// 队列为空,不操作
if (this.empty()) return;
// 头指针向后移动一位,逻辑上认为已出队,元素数量减1
this.head += 1;
this.cnt -= 1;
// 头指针走到最后一位,一样从0开始
if (this.head === this.arr.length) this.head = 0;
return;
}
// 判空
empty() {
return this.cnt === 0;
}
// 判满
fulll() {
return this.cnt === this.arr.length;
}
// 查看队首元素
front() {
if (this.empty()) return null;
return this.arr[this.head];
}
// 队列元素数量
size() {
return this.cnt;
}
output() {
let res = "";
// 循环cnt次,从head开始输出,走到最后一位,再从0开始
for (let i = 0, j = this.head; i < this.cnt; i++) {
res += this.arr[j];
j += 1;
if (j === this.arr.length) j = 0;
}
console.log(res);
}
}
function main() {
let arr = new Queue(5);
arr.push(1);
arr.push(2);
arr.push(3);
arr.pop();
arr.push(4);
arr.push(5);
arr.push(6);
arr.output();
}
main();
2.队列的典型应用场景
(1)场景一:CPU 的超线程技术
(2)场景二:线程池的任务队列
队列一般用作缓冲区
3.经典面试题
622. 设计循环队列
和前面实现循环队列类似
var MyCircularQueue = function(k) {
this.arr = new Array(k);
this.head = 0;
this.tail = 0;
this.cnt = 0;
};
MyCircularQueue.prototype.enQueue = function(value) {
if(this.isFull()) return false;
this.arr[this.tail] = value;
// 取余,超出边界时,循环到开头
this.tail = (this.tail + 1) % this.arr.length;
this.cnt += 1;
return true;
};
MyCircularQueue.prototype.deQueue = function() {
if(this.isEmpty()) return false;
// 取余,超出边界时,循环到开头
this.head = (this.head + 1) % this.arr.length;
this.cnt -= 1;
return true;
};
MyCircularQueue.prototype.Front = function() {
if(this.isEmpty()) return -1;
return this.arr[this.head];
};
MyCircularQueue.prototype.Rear = function() {
if(this.isEmpty()) return -1;
// let ind = this.tail - 1;
// if(ind === -1) ind = this.arr.length - 1;
// return this.arr[ind];
// 优化上面的代码,加数组长度再取余,处理负数的情况
return this.arr[(this.tail - 1 + this.arr.length) % this.arr.length];
};
MyCircularQueue.prototype.isEmpty = function() {
return this.cnt === 0;
};
MyCircularQueue.prototype.isFull = function() {
return this.cnt === this.arr.length;
};
641. 设计循环双端队列
和上题一样的技巧
var MyCircularDeque = function(k) {
this.arr = new Array(k);
this.head = 0;
this.tail = 0;
this.cnt = 0;
};
MyCircularDeque.prototype.insertFront = function(value) {
if(this.isFull()) return false;
this.head = (this.head - 1 + this.arr.length) % this.arr.length;
this.arr[this.head] = value;
this.cnt += 1;
return true;
};
MyCircularDeque.prototype.insertLast = function(value) {
if(this.isFull()) return false;
this.arr[this.tail] = value;
this.tail = (this.tail + 1) % this.arr.length;
this.cnt += 1;
return true;
};
MyCircularDeque.prototype.deleteFront = function() {
if(this.isEmpty()) return false;
this.head = (this.head + 1) % this.arr.length;
this.cnt -= 1;
return true;
};
MyCircularDeque.prototype.deleteLast = function() {
if(this.isEmpty()) return false;
this.tail = (this.tail - 1 + this.arr.length) % this.arr.length;
this.cnt -= 1;
return true;
};
MyCircularDeque.prototype.getFront = function() {
if(this.isEmpty()) return -1;
return this.arr[this.head];
};
MyCircularDeque.prototype.getRear = function() {
if(this.isEmpty()) return -1;
return this.arr[(this.tail - 1 + this.arr.length) % this.arr.length];
};
MyCircularDeque.prototype.isEmpty = function() {
return this.cnt === 0;
};
MyCircularDeque.prototype.isFull = function() {
return this.cnt === this.arr.length;
};
1670. 设计前中后队列
把前中后队列当成是两个双端队列,这里我们用数组来替代。设定元素是偶数时,两个双端队列元素各一半,是奇数时,前一个双端队列元素多一个
var FrontMiddleBackQueue = function() {
this.q1 = []; // 前一个队列
this.q2 = []; // 后一个队列
};
// 每个操作后都更新一下两个队列的元素数量
FrontMiddleBackQueue.prototype.pushFront = function(val) {
// q1头部入队
this.q1.unshift(val);
this.updata();
return;
};
FrontMiddleBackQueue.prototype.pushMiddle = function(val) {
// 奇数个时,需要先把q1尾部元素先挪动到q2头部,再插入q1
if(this.q1.length > this.q2.length){
this.q2.unshift(this.q1.pop());
}
// 偶数个时,直接插入q1
this.q1.push(val);
// 奇偶情况都考虑了,这里不updata也可以
return;
};
FrontMiddleBackQueue.prototype.pushBack = function(val) {
// q2尾部入队
this.q2.push(val);
this.updata();
return;
};
FrontMiddleBackQueue.prototype.popFront = function() {
if(this.isEmpty()) return -1;
// q1头部出队
let ret = this.q1.shift();
this.updata();
return ret;
};
FrontMiddleBackQueue.prototype.popMiddle = function() {
if(this.isEmpty()) return -1;
// 根据题意,总是q1尾部出队
let ret = this.q1.pop();
this.updata();
return ret;
};
FrontMiddleBackQueue.prototype.popBack = function() {
if(this.isEmpty()) return -1;
let ret;
// 有可能只剩下一个元素,所以判断q2是否为空,q2为空q1弹出元素
if(this.q2.length === 0){
ret = this.q1.pop();
}else{
ret = this.q2.pop();
}
this.updata();
return ret;
};
// 判空,因为q1总比q2多,所以判断q1中是否为空即可
FrontMiddleBackQueue.prototype.isEmpty = function() {
return this.q1.length === 0;
};
// 更新两个队列的元素数量,当有偶数个元素的时候,q1和q2平分,当有奇数个元素的时候,q1比q2多一个
FrontMiddleBackQueue.prototype.updata = function() {
if(this.q1.length < this.q2.length){
// q1元素少时,把q2头部元素插入q1尾部
this.q1.push(this.q2.shift());
}
if(this.q1.length === this.q2.length + 2){
// q1元素数量大于q2元素数量一个时,把q1尾部元素插入q2头部
this.q2.unshift(this.q1.pop());
}
return;
};
933. 最近的请求次数
每次把新请求插入到队列中,用新请求和队首元素进行比较,如果超过3000毫秒的话,队首元素就出队,最后返回当前队列元素数量即可
var RecentCounter = function() {
this.q = [];
};
RecentCounter.prototype.ping = function(t) {
this.q.push(t);
while(t - this.q[0] > 3000) this.q.shift();
return this.q.length;
};