有两种结构类似于数组,但在添加和删除元素时更加可控,它们就是栈和队列。
第四章 队列
队列数据结构
队列是遵循FIFO(First In First Out,先进先出,也称为先来先服务)原则的一组有序的项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。
现实中,很常见的例子就是排队。在计算机科学里面是打印队列。
创建队列
我们需要创建自己的类来表示一个队列,先从最基本的声明开始:
1 | function Queue(){ |
首先需要一个用于存储队列中元素的数据结构。我们可以使用数组,就像上一章 Stack 类中那样使用(你会发现其实两者很相似,只是添加和移除元素不一样而已。)
1 | let items = [] |
接下来需要声明一些队列可用的方法。
- enqueue(element(s)):向队列尾部添加一个(或多个)新的项。
- dequeue():移除队列中的第一个(排列在队伍最前面的)项,并返回被移除的元素
- front():返回队列中的第一个元素——最先被添加,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息——与 Stack 类的 peek 方法非常相似)
- isEmpty():如果队列中不包含任何元素,返回 ture,否则返回 false
- size():返回队列包含的元素个数,与数组的 length 属性类似。
向队列添加元素
首先要实现的是 enqueue 方法。这个方法负责向队列中添加新元素,还有一个非常重要的细节,新的项目只能添加到队列末尾:
1 | this.enqueue = function(element){ |
从队列中移除元素
接下来就是 dequeue 方法,这个方法负责从队列中移除项。由于队列遵循先进先出原则,最先添加的项也是要最先被移除的。数组中的 shift 方法会从数组中移除存储在索引0(第一个位置)的元素。
1 | this.dequeue = function(element){ |
只有 enqueue 方法和 dequeue 方法可以添加和移除元素,这样就确保了 Queue 类遵循先进先出的原则。
查看队列头元素
为我们类实现一些额外的辅助方法。我们想知道队列最前面是什么,可以使用 front 方法查看
1 | this.front = function(){ |
检查队列是否为空
1 | this.isEmpty = function(){ |
查看队列的长度
1 | this.size = function(){ |
打印队列元素
1 | this.print = function(){ |
实例
1 | function Queue(){ |
使用ES6 语法实现的 Queue 类
我们使用一个 WeakMap 来保存私有属性items,并用外层函数(闭包)来封装 Queue 类。
1 | let Queue = (function(){ |
优先队列
队列大量应用在计算机科学以及我们的生活中,其中一个就是优先队列。元素的添加和移除是基于优先级的。现实中的例子就是登机的顺序。头等舱和商务舱的乘客优先级要优于经济舱乘客。
另外一个现实的例子就是医院的候诊室。医生会优先处理病情比较严重的患者。
实现一个队列,有两种选项:设置优先级,然后在正确的位置添加元素;或者用入列操作添加元素,然后按照优先级操作它们。在这个实例中,我们会在正确的位置添加元素,因此可以对它们使用默认的出列操作。
1 | function ProrityQueue(){ |
循环队列——击鼓传花
还有另一个修改版的队列实现,就是循环队列。循环队列的一个例子就是击鼓传花游戏(Hot Potato)。在这个游戏中,孩子们围成一个圆圈,把花尽快地传递给旁边的人,某一时刻传花停止,这个时候,花就在谁的手里,谁就退出圆圈结束游戏。重复这个过程,直到最后一个孩子,就是胜者。
在这个例子中,我们要实现一个模拟的击鼓传花游戏。
1 | function Queue(){ |
下图模拟了这个输出过程:
可以改变传入 hotPotata 函数的数字,模拟不同的场景。
JavaScript 任务队列
当我们在浏览器中打开新标签时,就会创建一个任务队列。这是因为每个标签都是单线程处理所有的任务,它被称为 事件循环。浏览器要负责多个任务,如渲染 HTML ,执行 JavaScript 代码,处理用户交互(用户输入,鼠标点击等),执行和处理异步请求。
小结
这一章学习了队列这种数据结构。实现了自己的队列算法,学习了如何通过 enqueue 方法和 dequeue 方法添加和移除元素。还学习了两种非常著名的特殊队列的实现,优先队列和循环队列(使用击鼓传花的实现)
下一章,将学习链表,一种比数组更加复杂的数据结构。
书籍链接: 学习JavaScript数据结构与算法