目录
- 一、什么是JavaScript队列数据结构
- 二、创建一个JavaScript队列数据结构
- 三、封装队列方法
- ①向队列添加元素
- ②检查队列是否为空
- ③获取队列的长度
- ④从队列移除元素
- ⑤查看队列头元素
- ⑥清空队列
- ⑦创建toString方法
- 四、使用Queue类
一、什么是JavaScript队列数据结构
在上一篇文章中,我们了解并学习了JavaScript栈数据结构,本章开始JavaScript的队列数据结,第一章先认识队列与怎么去封装一个队列。
前面的文章里我们已经学习了栈。队列和栈非常类似,但是使用了与后进先出不同的原则。本章学习这些内容。
队列是遵循先进先出(FIFO,也称为先来先服务)原则的一组有序的项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。
在现实中,最常见的队列的例子就是排队,例如在火车站的售票处、超时里的收银台等,我们都会排队,排在第一位的人会先接受服务。
在计算机科学中,一个常见的例子就是打印队列。比如说我们需要打印五份文档。我们会打开每个文档,然后点击打印按钮,每个文档都会被发送至打印队列。第一个发送到打印队列的文档会首先被打印,以此类推,直到打印完所有文档。
二、创建一个JavaScript队列数据结构
我们需要创建自己的类来表示一个队列。先从最基本的声明类开始。
class Queue {
constructor(){
this.count = 0; //{1}
this.lowestCount = 0; //{2}
this.items = {}; //{3}
}
}
首先需要一个用于存储队列中元素的数据结构。我们可以使用数组,就像上一章的Stack类那样。
但是,为了写出一个在获取元素时更高效的数据结构,我们将使用一个对象来存储我们的元素(行{3})。
你会发现Queue类和Stack类非常类似,只是添加和移除元素的原则不同。
也可以声明一个count属性来帮助我们控制队列的大小(行{1})。
此外,由于我们将要从队列前端移除元素,同样需要一个变量来帮助我们追踪第一个元素。因此,声明一个lowestCount变量(行{2})。
接下来需要声明一些队列可用的方法:
- enqueue(element(s)):向队列尾部添加一个(或多个)新的项。
- isEmpty():如果队列中不包含任何元素,返回true,否则返回false。
- size():返回队列包含的元素个数,与数组的length属性类似。
- dequeue():移除队列的第一项(即排在队列最前面的项)并返回被移除的元素。
- peek():返回队列中第一个元素——最先被添加,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息——与Stack类的peek方法非常类似)。该方法在其他语言中也可以叫作front 方法。
- clear():清空整个队列。
- toString():将队列转为字符串返回。
下一小节来封装并实现这些方法。
三、封装队列方法
①向队列添加元素
首先要实现的是enqueue方法。该方法负责向队列添加新元素。此处一个非常重要的细节是新的项只能添加到队列末尾。
class Queue {
constructor(){
this.count = 0; //{1}
this.lowestCount = 0; //{2}
this.items = {}; //{3}
}
enqueue(element){
this.items[this.count] = element;
this.count++;
}
}
enqueue 方法和Stack 类中 push 方法的实现方式相同。由于items 属性是一个JavaScript对象,它是一个键值对的集合。要向队列中加人一个元素的话,我们要把count变量作为items对象中的键,对应的元素作为它的值。将元素加入队列后,我们将count变量加1。
②检查队列是否为空
刚刚实现了向队列添加元素的方法,这个要实现是isEmpty方法。如果队列为空,它会返回true,否则返回false(注意该方法和Stack类里的一样)。
class Queue {
constructor(){
this.count = 0; //{1}
this.lowestCount = 0; //{2}
this.items = {}; //{3}
}
enqueue(element){
this.items[this.count] = element;
this.count++;
}
isEmpty(){
return this.count - this.lowestCount === 0;
}
}
③获取队列的长度
要计算队列中有多少元素,我们只需要计算count和lowestCount之间的差值即可。
假设count属性的值为2,lowestCount的值为0。这表示在队列中有两个元素。然后,从队列中移除一个元素,lowestCount的值会变为1,count的值仍然是2。现在队列中只有一个元素了,以此类推。
所以要实现size方法的话,我们只需要返回这个差值即可。
class Queue {
constructor(){
this.count = 0; //{1}
this.lowestCount = 0; //{2}
this.items = {}; //{3}
}
enqueue(element){
this.items[this.count] = element;
this.count++;
}
isEmpty(){
return this.count - this.lowestCount === 0;
}
size(){
return this.count - this.lowestCount;
}
/* 也可以像这样实现 isEmpty 方法*/
// isEmpty(){
// return this.size() === 0;
// }
}
④从队列移除元素
接下来要实现dequeue方法,该方法负责从队列移除项。由于队列遵循先进先出原则,最先添加的项也是最先被移除的。
class Queue {
constructor(){
this.count = 0; //{1}
this.lowestCount = 0; //{2}
this.items = {}; //{3}
}
enqueue(element){
this.items[this.count] = element;
this.count++;
}
isEmpty(){
return this.count - this.lowestCount === 0;
}
size(){
return this.count - this.lowestCount;
}
/* 也可以像这样实现 isEmpty 方法*/
// isEmpty(){
// return this.size() === 0;
// }
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}
}
}
首先,我们需要检验队列是否为空。如果为空,我们返回 undefined值。如果队列不为空,我们将暂存队列头部的值(行{1}),以便该元素被移除后(行{2})将它返回(行{4})。我们也需要将lowestCount属性加1(行{3})。
用下面的内部值来模拟dequeue动作。
items = {
0: 5,
1: 9
}
count = 2;
lowestCount = 0;
我们需要将键设为0来获取队列头部的元素(第一个被添加的元素是5),删除它,再返回它的值。
在这种场景下,删除第一个元素后,items属性将只会包含一个元素(1:9)。
再次执行dequeue方法的话,它将被移除,因此我们将lowestCount变量从0修改为1。
只有enqueue 方法和 dequeue 方法可以添加和移除元素,这样就确保了Queue类遵循先进先出原则。
⑤查看队列头元素
现在来为我们的Queue类实现一些额外的辅助方法。如果想知道队列最前面的项是什么,可以用peek方法。该方法会返回队列最前面的项(把lowestCount作为键名来获取元素值)。
class Queue {
constructor(){
this.count = 0; //{1}
this.lowestCount = 0; //{2}
this.items = {}; //{3}
}
enqueue(element){
this.items[this.count] = element;
this.count++;
}
isEmpty(){
return this.count - this.lowestCount === 0;
}
size(){
return this.count - this.lowestCount;
}
/* 也可以像这样实现 isEmpty 方法*/
// isEmpty(){
// return this.size() === 0;
// }
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}
}
peek(){
if(this.isEmpty()){
return undefined;
}
return this.items[this.lowestCount];
}
}
⑥清空队列
若要清空队列中的所有元素,我们可以调用dequeue方法直到它返回 undefined,也可以简单地将队列中的属性值重设为和构造函数中的一样。
class Queue {
constructor(){
this.count = 0; //{1}
this.lowestCount = 0; //{2}
this.items = {}; //{3}
}
enqueue(element){
this.items[this.count] = element;
this.count++;
}
isEmpty(){
return this.count - this.lowestCount === 0;
}
size(){
return this.count - this.lowestCount;
}
/* 也可以像这样实现 isEmpty 方法*/
// isEmpty(){
// return this.size() === 0;
// }
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}
}
peek(){
if(this.isEmpty()){
return undefined;
}
return this.items[this.lowestCount];
}
clear(){
this.count = 0; //{1}
this.lowestCount = 0; //{2}
this.items = {}; //{3}
}
}
⑦创建toString方法
其实呢到这里,一个简单的Queue类就已经完成了!我们也可以像Stack类一样增加一个toString方法。
class Queue {
constructor(){
this.count = 0; //{1}
this.lowestCount = 0; //{2}
this.items = {}; //{3}
}
enqueue(element){
this.items[this.count] = element;
this.count++;
}
isEmpty(){
return this.count - this.lowestCount === 0;
}
size(){
return this.count - this.lowestCount;
}
/* 也可以像这样实现 isEmpty 方法*/
// isEmpty(){
// return this.size() === 0;
// }
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}
}
peek(){
if(this.isEmpty()){
return undefined;
}
return this.items[this.lowestCount];
}
clear(){
this.count = 0; //{1}
this.lowestCount = 0; //{2}
this.items = {}; //{3}
}
toString(){
if(this.isEmpty()){
return '';
}
let objString = `${this.items[this.lowestCount]}`;
for(let i = this.lowestCount + 1; i < this.count; i++){
objString += `${objString},${this.items[i]}`;
}
return objString;
}
}
在Stack类中,我们从索引值为0开始选代items中的值。由于Queue类中的第一个索引值不一定是0,我们需要从索引值为lowestCount的位置开始迭代队列。
到现在为止呢,我们真的完成了!
Queue 类和Stack类非常类似。主要的区别在于dequeue方法和peek方法,这是由于先进先出和后进先出原则的不同所造成的。
四、使用Queue类
首先要做的是实例化我们刚刚创建的Queue类,然后就可以验证它为空(输出为true,因为我们还没有向队列添加任何元素)。
const queue = new Queue();
console.log(queue.isEmpty()); //输出true
接下来,添加一些元素,添加张三和李四两个元素——现在可以向队列添加任何类型的元素。
queue.enqueue('张三');
queue.enqueue('李四');
console.log(queue.toString()); //输出 张三,李四
再添加另一个元素。
queue.enqueue('王五');
console.log(queue.toString()); //输出 张三,李四,王五
console.log(queue.size()); //输出 3
console.log(queue.isEmpty()); //输出false
queue.dequeue(); // 移除张三
4如果打印队列的内容,就会得到张三、李四、王五这三个元素。因为我们向队列添加了三个元素,所以队列的大小为3(当然也就不为空了)。
下图展示了目前为止执行的所有入列操作,以及队列当前的状态。
也就是说,我们遵循了先进先出原则。