javascript:线程池与任务队列(Task-Queue)

247 阅读2分钟

1.队列基础知识

  1. 队列有一片连续的存储区,可以是数组,也可以是链表,可以存储任意类型的元素
  2. 队列有两个指针,一个头指针,一个尾指针,头指针指向第一个节点尾指针指向最后一个节点的下一位(左闭右开,尾指针减去头指针就是元素的数量)。头部出队,尾部入队,先入先出(FIFO)
  3. 标准队列支持两种基本操作,出队和入队
  • 队首出队,头指针向后移动一位,在逻辑层面,认为元素已出队
  • 队尾入队,把数据添加进来,尾指针向后移动一位
  1. 允许中间入队的队列称为优先队列
  2. 假溢出:其实还有位置可以放入其他元素,只是因为尾指针走到了最后一位,普通队列会误以为队列满了,为了解决假溢出,提出循环队列
  • 循环队列:走到尾部,再从头开始

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;
};