队列 (Queue) 及其经典问题

506 阅读4分钟

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。

什么是队列

  • 一般情况,队列是一片连续的存储区,里面可以存储任意类型的数据
  • 队列有两个指针,通常情况下,一个指向头部元素,一个指向尾部元素的下一位
  • 普通队列一般有两个操作,
    • 出队:头指针,向后移动一位,完成头部元素的逻辑出队操作
    • 入队:尾指针,在尾指针处插入新的元素,然后尾指针向后移动一位,完成入队操作
  • 普通队列出队入队的顺序,一般是 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 资源,在硬件层面上,可以通过超线程技术,模拟出在外界看来是双倍虚拟核心的表象,其实就是单个核心从双倍的 任务队列 中读取任务进行计算,提高任务的执行效率

image.png

线程池的任务队列

  • 进程是资源的总和,一个进程可以包含若干个线程
  • 普通的多线程,会出现频繁的创建与销毁线程的情况,这个过程其实非常消耗性能
  • 线程池可以很好的解决上述问题,但是线程池的线程数量是有限的
    • 任务输入之后,会依次分配给空闲的线程进行计算
    • 有未被分配的任务,则会缓冲到 任务队列 中,当有线程空闲之后,再从 任务队列 中取出来让该线程执行 image.png

最后

  • 队列的分享就到这里了,欢迎大家在评论区里面讨论自己的理解 👏。
  • 如果觉得文章写的不错的话,希望大家不要吝惜点赞,大家的鼓励是我分享的最大动力 🥰