数据结构系列-队列和双端队列(javascript任务队列)

队列是遵循先进先出(FIFO,也称为先来先服务)原则的一组有序的项。

队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。

  class Queue {
      constructor() {
        this.count = 0; // {1}
        this.lowestCount = 0; // {2}
        this.items = {}; // {3}
      } 
  }

为了写出一个在获取元素时更高效的数据结构,我们将使用一个对象来存储我们 的元素(行{3})。

你会发现 Queue 类和 Stack 类(栈)非常类似,只是添加和移除元素的原则不同。

队列的常用方法:

  •   enqueue(element(s)):向队列尾部添加一个(或多个)新的项。

  •   dequeue():移除队列的第一项(即排在队列最前面的项)并返回被移除的元素。

  •   peek():返回队列中第一个元素——最先被添加,也将是最先被移除的元素。队列不做

        任何变动(不移除元素,只返回元素信息——与 Stack 类的 peek 方法非常类似)。

        该方 0 法在其他语言中也可以叫作 front 方   法。

  •   isEmpty():如果队列中不包含任何元素,返回 true,否则返回 false。

  •   size():返回队列包含的元素个数,与数组的 length 属性类似。

具体实现方法这里不再实现,具体参考《学习javascript数据结构和算法》

这里介绍一个移除方法dequeue()

dequeue() {
      if (this.isEmpty()) {
        return undefined;
      }
      const result = this.items[this.lowestCount]; // {1}
      delete this.items[this.lowestCount]; // {2}
      this.lowestCount++; // {3}
      return result; // {4}
}

由于queue采用count作为key值,所以要访问到每一个元素就要知道其key值,对于队列最前端的元素每次移除后,lowestCount要指向下一个key值,所以要+1

双端队列数据结构


双端队列(deque,或称 double-ended queue)是一种允许我们同时从前端和后端添加和移除元素的特殊队列。

在计算机科学中,双端队列的一个常见应用是存储一系列的撤销操作。每当用户在软件中进 行了一个操作,该操作会被存在一个双端队列中(就像在一个栈里)。当用户点击撤销按钮时, 该操作会被从双端队列中弹出,表示它被从后面移除了。在进行了预先定义的一定数量的操作后, 最先进行的操作会被从双端队列的前端移除。由于双端队列同时遵守了先进先出和后进先出原 则,可以说它是把队列和栈相结合的一种数据结构。

class Deque {
    constructor() {
        this.count = 0;
        this.lowestCount = 0;
        this.items = {}; 
    } 
}

既然双端队列是一种特殊的队列,我们可以看到其构造函数中的部分代码和队列相同,包括 相同的内部属性和以下方法:isEmpty、clear、size 和 toString。

由于双端队列允许在两端添加和移除元素,还会有下面几个方法。

addFront(element):该方法在双端队列前端添加新的元素。

addBack(element):该方法在双端队列后端添加新的元素(实现方法和 Queue 类中的 enqueue 方法相同)。

removeFront():该方法会从双端队列前端移除第一个元素(实现方法和 Queue 类中的 dequeue 方法相同)。

removeBack():该方法会从双端队列后端移除第一个元素(实现方法和 Stack 类中的 pop 方法一样)。
peekFront():该方法返回双端队列前端的第一个元素(实现方法和 Queue 类中的 peek 方法一样)。
peekBack():该方法返回双端队列后端的第一个元素(实现方法和 Stack 类中的 peek 方法一样)。

这里介绍一个双端队列添加元素方法:

 addFront(element) {
      if (this.isEmpty()) { // {1}
        this.addBack(element);
      } else if (this.lowestCount > 0) { // {2}
        this.lowestCount--;
        this.items[this.lowestCount] = element;
      } else {
        for (let i = this.count; i > 0; i--) { // {3}
          this.items[i] = this.items[i - 1];
        }
        this.count++;
        this.lowestCount = 0;
        this.items[0] = element; // {4}
      } 
}

说明:要将一个元素添加到双端队列的前端,存在三种场景。

第一种场景是这个双端队列是空的(行{1})。在这种情况下,我们可以执行 addBack 方法。 元素会被添加到双端队列的后端,在本例中也是双端队列的前端。addBack 方法已经有了增加 count 属性值的逻辑,因此我们可以复用它来避免重复编写代码。

第二种场景是一个元素已经被从双端队列的前端移除(行{2}),也就是说 lowestCount 属 性会大于等于 1。这种情况下,我们只需要将 lowestCount 属性减 1 并将新元素的值放在这个

第三种也是最后一种场景是 lowestCount 为 0 的情况。我们可以设置一个负值的键,同时 更新用于计算双端队列长度的逻辑,使其也能包含负键值。这种情况下,添加一个新元素的操作 仍然能保持最低的计算成本。为了便于演示,我们把本场景看作使用数组。要在第一位添加一个 新元素,我们需要将所有元素后移一位(行{3})来空出第一个位置。由于我们不想丢失任何已 有的值,需要从最后一位开始迭代所有的值,并为元素赋上索引值减 1 位置的值。在所有的元素 7 都完成移动后,第一位将是空闲状态,这样就可以用需要添加的新元素来覆盖它了(行{4})

实例:

循环队列——击鼓传花游戏

由于队列经常被应用在计算机领域和我们的现实生活中,就出现了一些队列的修改版本,我 们会在本章实现它们。这其中的一种叫作循环队列。循环队列的一个例子就是击鼓传花游戏(hot potato)。在这个游戏中,孩子们围成一个圆圈,把花尽快地传递给旁边的人。某一时刻传花停止, 这个时候花在谁手里,谁就退出圆圈、结束游戏。重复这个过程,直到只剩一个孩子(胜者)。

function hotPotato(elementsList, num) {
    const queue = new Queue(); // {1}
    const elimitatedList = [];
    for (let i = 0; i < elementsList.length; i++) {
        queue.enqueue(elementsList[i]); // {2}
    }
    while (queue.size() > 1) {
        for (let i = 0; i < num; i++) {
            queue.enqueue(queue.dequeue()); // {3}
        }
        elimitatedList.push(queue.dequeue()); // {4}
    }
    return {
        eliminated: elimitatedList,
        winner: queue.dequeue() // {5}
    }; 
}

还有一种实例是回文判断,原理是使用双端队列,判断从队首和队尾移除的元素是否相等

 

队列在javascript中的体现:JavaScript任务队列

当我们在浏览器中打开新标签时,就会创建一个任务队列。

这是因为每个标签都是单线程处 理所有的任务,称为事件循环。

浏览器要负责多个任务,如渲染 HTML、执行 JavaScript 代码、 处理用户交互(用户输入、鼠标点击等)、执行和处理异步请求。如果想更多地了解事件循环, 可以访问 https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值