上一篇系列文章《【数据结构基础】栈简介(使用ES6)》笔者介绍了什么是数据结构和什么是栈及相关代码实现,本篇文章笔者给你们介绍下什么是队列以及相关的代码实现。前端
本篇文章将从如下几个方面进行介绍:算法
本篇文章阅读时间预计10分钟。数组
队列是一个有序集合,遵循先进先出的原则(FIFO),与堆栈的原则偏偏相反。容许插入的一端称为队尾,容许删除的一端称为对头。假设队列是q=(a1,a2,......,an),那么a1就是队头,an就是队尾。咱们删除时,从a1开始删除,而插入时,只能在an后插入。浏览器
队列就比如咱们生活中的排队,好比咱们去医院挂号须要排队,进电影院须要排队进场,去超市买东西须要排队结帐,打电话咨询客服须要排队接听等等。bash
在计算机中最多见的例子就是打印机的打印队列任务,假设咱们要打印五分不一样的文档,咱们须要依次打开每一个文档,依次的单击“打印按钮”,每一个打印指令都会送往打印队列任务,最早按打印按钮的文档最早被打印,直到全部文档被打印完成。微信
首先咱们先声明建立一个初始化的queue类,实现代码以下:数据结构
class Queue {
constructor() {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
} 复制代码
首先咱们建立了一个存储队列元素的数据结构,咱们声明了count变量,方便咱们统计队列大小,声明lowestCount变量标记队列的对头,方便咱们删除元素。接下来咱们要声明以下方法,来实现一个完整的队列:ide
此方法主要实现了向队列的队尾添加新的元素,实现的关键就是“队尾”添加元素,实现代码以下:函数
enqueue(element) {
this.items[this.count] = element;
this.count++;
} 复制代码
因为队列的items属性是对象,咱们使用count做为对象的属性,元素添加至队列后,count的值递增长1。oop
此方法主要用于删除队列元素,因为队列遵循先进先出原则,咱们须要将队列的“队头”元素进行移除,代码实现以下:
dequeue() {
if (this.isEmpty()) {
return undefined;
}
const result = this.items[this.lowestCount];
delete this.items[this.lowestCount];
this.lowestCount++;
return result;
}复制代码
首先咱们须要验证队列是否为空,若是未空返回未定义。若是队列不为空,咱们首先获取“队头”元素,而后使用delete方法进行删除,同时标记对头元素的变量lowestCount递增长一,而后返回删除的队头元素。
如今咱们来实现一些辅助方法,好比咱们想查看“队头”元素,咱们用peek()方法进行实现,使用lowestCount变量进行获取,实现代码以下:
peek() {
if (this.isEmpty()) {
return undefined;
}
return this.items[this.lowestCount];
} 复制代码
获取队列的长度,咱们可使用count变量与lowestCount相减便可,假如咱们的count属性为2,lowestCount为0,这意味着队列有两个元素。接下来咱们从队列里中删除一个元素,lowestCount的值更新为1,count的值不变,所以队列的长度为1,依次类推。所以size()方法的实现代码以下:
size() {
return this.count - this.lowestCount;
} 复制代码
isEmpty()的实现方式更为简单了,只须要判断size()是否返回为0便可,实现代码以下:
isEmpty() {
return this.size() === 0;
}复制代码
要清空队列元素,咱们能够一直调用dequeue()方法,直至返回undefined便可或者将各变量重置为初始值便可,咱们使用重置初始化的思路,代码以下:
clear() {
this.items = {};
this.count = 0;
this.lowestCount = 0;
}复制代码
接下来咱们实现最后一个方法,打印输出队列全部的元素,示例代码以下:
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;
} 复制代码
export default class Queue {
constructor() {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
enqueue(element) {
this.items[this.count] = element;
this.count++;
}
dequeue() {
if (this.isEmpty()) {
return undefined;
}
const result = this.items[this.lowestCount];
delete this.items[this.lowestCount];
this.lowestCount++;
return result;
}
peek() {
if (this.isEmpty()) {
return undefined;
}
return this.items[this.lowestCount];
}
isEmpty() {
return this.size() === 0;
}
clear() {
this.items = {};
this.count = 0;
this.lowestCount = 0;
}
size() {
return this.count - this.lowestCount;
}
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;
}
}复制代码
首先引入咱们的Queue类,而后初始化建立咱们的Queue类,验证是否为空,而后进行添加删除元素,示例代码以下:
const queue = new Queue();
console.log(queue.isEmpty()); // outputs true
queue.enqueue('John');
queue.enqueue('Jack');
console.log(queue.toString()); // John,Jack
queue.enqueue('Camila');
console.log(queue.toString()); // John,Jack,Camila
console.log(queue.size()); // outputs 3
console.log(queue.isEmpty()); // outputs false
queue.dequeue(); // remove John
queue.dequeue(); // remove Jack
console.log(queue.toString()); // Camila复制代码
以下图所示演示了上述代码的执行效果:
双端队列是一个特殊的更灵活的队列,咱们能够在队列的“队头”或“队尾”添加和删除元素。因为双端队列是实现了FIFO和LIFO这两个原则,也能够说是队列和堆栈结构的合体结构。
在咱们生活中,好比排队买票,有的人着急或特殊状况,直接来到队伍的最前面,有的人由于其余的事情,等不了太长时间,从队尾离开了。
首先咱们声明初始化一个双端队列,代码和队列的结构相似,以下段代码所示:
class Deque {
constructor() {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
}复制代码
因为双端队列的结构和队列的结构相似,只是插入和删除更灵活而已,isEmpty(), clear(), size()和toString()相关方法保持一致,还须要增长如下相关的方法:
因为从双端队列的的“队头”添加元素,稍微复杂些,实现代码以下:
addFront(element) {
if (this.isEmpty()) {
this.addBack(element);
} else if (this.lowestCount > 0) {
this.lowestCount--;
this.items[this.lowestCount] = element;
} else {
for (let i = this.count; i > 0; i--) {
this.items[i] = this.items[i - 1];
}
this.count++;
this.lowestCount = 0;
this.items[0] = element;
}
}复制代码
从上述代码咱们能够看出,若是双端队列为空,咱们复用了addBack()方法,避免书写重复的代码;若是队头元素lowestCount的变量大于0,咱们将变量递减,将新添加的元素赋值给队头元素;若是lowestCount的变量为0,为了不负值的出现,咱们将队列元素总体日后移动1位,进行从新赋值,将队头索引为0的位置留给新添加的元素。
因为文章篇幅有限,其余的方法又很相似,再也不一一介绍,完整的代码以下:
export default class Deque {
constructor() {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
addFront(element) {
if (this.isEmpty()) {
this.addBack(element);
} else if (this.lowestCount > 0) {
this.lowestCount--;
this.items[this.lowestCount] = element;
} else {
for (let i = this.count; i > 0; i--) {
this.items[i] = this.items[i - 1];
}
this.count++;
this.items[0] = element;
}
}
addBack(element) {
this.items[this.count] = element;
this.count++;
}
removeFront() {
if (this.isEmpty()) {
return undefined;
}
const result = this.items[this.lowestCount];
delete this.items[this.lowestCount];
this.lowestCount++;
return result;
}
removeBack() {
if (this.isEmpty()) {
return undefined;
}
this.count--;
const result = this.items[this.count];
delete this.items[this.count];
return result;
}
peekFront() {
if (this.isEmpty()) {
return undefined;
}
return this.items[this.lowestCount];
}
peekBack() {
if (this.isEmpty()) {
return undefined;
}
return this.items[this.count - 1];
}
isEmpty() {
return this.size() === 0;
}
clear() {
this.items = {};
this.count = 0;
this.lowestCount = 0;
}
size() {
return this.count - this.lowestCount;
}
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;
}
}复制代码
接下来咱们来验证下咱们的Deque类,首先引入Deque类的文件,代码以下:
const deque = new Deque();
console.log(deque.isEmpty()); // outputs true
deque.addBack('John');
deque.addBack('Jack');
console.log(deque.toString()); // John,Jack
deque.addBack('Camila');
console.log(deque.toString()); // John,Jack,Camila
console.log(deque.size()); // outputs 3
console.log(deque.isEmpty()); // outputs false
deque.removeFront(); // remove John
console.log(deque.toString()); // Jack,Camila
deque.removeBack(); // Camila decides to leave
console.log(deque.toString()); // Jack
deque.addFront('John'); // John comes back for information
console.log(deque.toString()); // John,Jack”复制代码
不知道你们玩过击鼓传花吗,笔者最怕玩这个,不知道是点背还在咋地,这个花球总和我有缘,自己就五音不全还要表演,人可丢大了。什么是击鼓传花,在这里给没玩过的朋友解释下:数人或几十人围成圆圈坐下,其中一人拿花(或一小物件);另有一人背着你们或蒙眼击鼓(桌子、黑板或其余能发出声音的物体),鼓响时众人开始依次传花,至鼓中止为止。此时花在谁手中(或其座位前),谁就上台表演节目(可能是唱歌、跳舞、说笑话;或回答问题、猜谜、按纸条规定行事等);偶然若是花在两人手中,则两人可经过猜拳或其它方式决定负者。
今天咱们要用队列实现这个游戏,稍微不一样的是,拿到花球的人须要出列,直到最后一个拿到花球的人获胜。假设告诉敲鼓的人一个数字(从0开始),按照数字循环在场的人,到达这个数字中止敲鼓,直到最后一我的为止。
你们是否是火烧眉毛的想知道代码如何实现?代码以下所示:
function hotFlower(elementsList, num) {
const queue = new Queue();
const elimitatedList = [];
for (let i = 0; i < elementsList.length; i++) {
queue.enqueue(elementsList[i]);
}
while (queue.size() > 1) {
for (let i = 0; i < num; i++) {
queue.enqueue(queue.dequeue());
}
elimitatedList.push(queue.dequeue());
}
return {
eliminated: elimitatedList,
winner: queue.dequeue()
};
}复制代码
从上述代码咱们能够看出:
接下来咱们来验证下,此算法是否正确,验证代码以下:
const names = ['John', 'Jack', 'Camila', 'Ingrid', 'Carl'];
const result = hotFlower(names, 7);
result.eliminated.forEach(name => {
console.log(`${name} was eliminated from the Hot Flower game.`);
});
console.log(`The winner is: ${result.winner}`);复制代码
上述代码将会输出:
Camila was eliminated from the Hot Flower game.
Jack was eliminated from the Hot Flower game.
Carl was eliminated from the Hot Flower game.
Ingrid was eliminated from the Hot Flower game.
The winner is: John复制代码
代码运行时,队列的变化示意图以下:
许多英语单词不管是顺读仍是倒读,其词形和词义彻底同样,如dad(爸爸)、noon(中午)、level(水平)等。最简单的方法就是反转字符串与原始字符串进行比较是否相等。从数据结构的角度咱们能够运用堆栈的结构进行实现,然而用双端队列的结构实现起来也很是简单,示例代码以下:
function palindromeChecker(aString) {
if (aString === undefined || aString === null ||
(aString !== null && aString.length === 0)) {
return false;
}
const deque = new Deque();
const lowerString = aString.toLocaleLowerCase().split(' ').join('');
let isEqual = true;
let firstChar, lastChar;
for (let i = 0; i < lowerString.length; i++) {
deque.addBack(lowerString.charAt(i));
}
while (deque.size() > 1 && isEqual) {
firstChar = deque.removeFront();
lastChar = deque.removeBack();
if (firstChar !== lastChar) {
isEqual = false;
}
}
return isEqual;
}复制代码
从上述代码咱们能够看出:
接下来咱们来验证下咱们的算法是否正确:
console.log('a', palindromeChecker('a'));
console.log('aa', palindromeChecker('aa'));
console.log('kayak', palindromeChecker('kayak'));
console.log('level', palindromeChecker('level'));
console.log('Was it a car or a cat I saw', palindromeChecker('Was it a car or a cat I saw'));
console.log('Step on no pets', palindromeChecker('Step on no pets'));复制代码
上述代码的运行结果都返回为true。
今天关于队列的介绍就到这里,咱们一块儿学习了什么是队列和双端队列,以及如何进行代码实现。而且运用循环队列的机制实现了击鼓传花的游戏,同时又运用双端队列的结构实现了回文的验证。其实队列在咱们的实际业务场景中运用仍是蛮多的,好比咱们要实现一个队列的消息推送机制,咱们JS的event loop的时间循环机制,浏览器的页面渲染机制等等。但愿本篇的内容对你们有所帮助,在实践中运用多了才能运用队列的机制解决更多的实际问题。