「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。
什么是队列
- 一般情况,队列是一片连续的存储区,里面可以存储任意类型的数据
- 队列可以是数组,当然也可以是链表结构,如 leetcode 1670.设计前中后队列 就可以使用链表来实现前中后队列
- 队列有两个指针,通常情况下,一个指向头部元素,一个指向尾部元素的下一位
- 普通队列一般有两个操作,
- 出队:头指针,向后移动一位,完成头部元素的逻辑出队操作
- 入队:尾指针,在尾指针处插入新的元素,然后尾指针向后移动一位,完成入队操作
- 普通队列出队入队的顺序,一般是 FIFO,先入先出
- 普通队列可能出现假溢出的情况
- 经过几次入队出队操作后,尾指针可能已经移动到队尾,而头指针由于出队操作导致其不在队首
- 这样就出现了队列无法插入新元素,但实际的队列空间并没有满的情况
- 为了解决普通队列的假溢出,更高效的利用队列空间,就出现了 循环队列
- 无论是头指针还是尾指针,移动到队尾之后,判断一下队列元素个数是否等于队列长度
- 如队列还没有满,则将指针移动至队首
队列的实现方式
普通队列
class Queue {
constructor(n = 10) {
this.head = 0; // 头指针
this.tail = 0; // 尾指针
this.items = []; // 存放队列的数据
this.maxSize = n; // 队列长度
}
// 入队
push(val) {
if (this.isFull()) {
console.log(`队列已满,入不了队了,head: ${this.head}, tail: ${this.tail}`);
return;
}
this.items[this.tail] = val;
// tail 指向队尾元素的下一位
++this.tail;
this.output();
}
// 出队
pop() {
if (this.isEmpty()) {
console.log(`队列已空,出不了队了,head: ${this.head}, tail: ${this.tail}`);
return;
}
++this.head;
this.output();
}
// 判空
isEmpty() {
return this.tail === this.head;
}
// 判满
isFull() {
return this.tail === this.maxSize;
}
// 获取队列当前的元素数量
getSize() {
return this.tail - this.head;
}
// 获取队首元素
getFront() {
if (this.isEmpty()) {
return undefined;
}
return this.items[this.head];
}
// 输出队列中的所有元素
output() {
let list = [];
for (let index = this.head; index < this.tail; index++) {
list.push(this.items[index]);
}
console.log(list.join(", "));
}
}
let queue = new Queue(2);
queue.push(11); // 11
queue.push(22); // 11, 22
queue.push(33); // 队列已满,入不了队了,head: 0, tail: 2
console.log("size: ", queue.getSize()); // size: 2
queue.pop(); // 22
console.log("fonrt: ", queue.getFront()); //fonrt: 22
queue.pop(); // [空]
queue.pop(); // 队列已空,出不了队了,head: 2, tail: 2
循环队列
class Queue {
constructor(n = 10) {
this.head = 0; // 头指针
this.tail = 0; // 尾指针
this.items = []; // 存放队列的数据
this.maxSize = n; // 队列长度
this.count = 0; // 当前元素数量
}
// 入队
push(val) {
if (this.isFull()) {
console.log(`队列已满,入不了队了,head: ${this.head}, tail: ${this.tail}`);
return;
}
this.items[this.tail] = val;
// tail 指向队尾元素的下一位
++this.tail;
// 当前队列元素个数加一
++this.count;
// 循环起来
if (this.tail === this.maxSize) {
this.tail = 0;
}
this.output();
}
// 出队
pop() {
if (this.isEmpty()) {
console.log(`队列已空,出不了队了,head: ${this.head}, tail: ${this.tail}`);
return;
}
// 头指针往后走一步,完成出队操作
++this.head;
// 当前队列元素个数减一
--this.count;
// 循环起来
if (this.head === this.maxSize) {
this.head = 0;
}
this.output();
}
// 判空
isEmpty() {
// return this.tail === this.head;
return this.count === 0;
}
// 判满
isFull() {
// return this.tail === this.maxSize;
return this.count === this.maxSize;
}
// 获取队列当前的元素数量
getSize() {
// return this.tail - this.head;
return this.count;
}
// 获取队首元素
getFront() {
if (this.isEmpty()) {
return undefined;
}
return this.items[this.head];
}
// 输出队列中的所有元素
output() {
let list = [];
// for (let index = this.head; index < this.tail; index++) {
// list.push(this.items[index]);
// }
for (let index = this.head, n = 0; n < this.count; n++) {
list.push(this.items[index]);
index++;
if (index === this.maxSize) {
index = 0;
}
}
console.log(list.join(", "));
}
}
let queue = new Queue(3);
queue.push(11); // 11
queue.push(22); // 11, 22
queue.push(33); // 11, 22, 33
queue.push(44); // 队列已满,入不了队了,head: 0, tail: 0
console.log("size: ", queue.getSize()); // size: 3
queue.pop(); // 22, 33
queue.push(55); // 22, 33, 55
queue.push(66); // 队列已满,入不了队了,head: 1, tail: 1
console.log("fonrt: ", queue.getFront()); //fonrt: 22
queue.pop(); // 33, 55
queue.pop(); // 55
queue.pop(); // [空]
queue.pop(); // 队列已空,出不了队了,head: 1, tail: 1
队列的典型应用场景
CPU 的超线程技术
- 个 CPU 核心处理任务指令的速度非常快,往往任务指令输入的速度是跟不上 CPU 核心处理任务的速度的
- 为了能更高效的利用核心的计算资源,CPU 不必等待任务指令的输入过程,在 CPU 处理其他任务的同时,可以将输入的任务指令暂时缓冲在
任务队列中,等 CPU 空闲时再从任务队列中,取出任务进行计算 - 如下图所示,为了能高效的使用 CPU 资源,在硬件层面上,可以通过超线程技术,模拟出在外界看来是双倍虚拟核心的表象,其实就是单个核心从双倍的
任务队列中读取任务进行计算,提高任务的执行效率
线程池的任务队列
- 进程是资源的总和,一个进程可以包含若干个线程
- 普通的多线程,会出现频繁的创建与销毁线程的情况,这个过程其实非常消耗性能
- 线程池可以很好的解决上述问题,但是线程池的线程数量是有限的
- 任务输入之后,会依次分配给空闲的线程进行计算
- 有未被分配的任务,则会缓冲到
任务队列中,当有线程空闲之后,再从任务队列中取出来让该线程执行
最后
- 队列的分享就到这里了,欢迎大家在评论区里面讨论自己的理解 👏。
- 如果觉得文章写的不错的话,希望大家不要吝惜点赞,大家的鼓励是我分享的最大动力 🥰