队列数据结构
队列是遵循先进先出(FIFO,先来先服务)原则的一组有序项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。
生活中队列的例子:排队,打印队列(文档)。
创建 Queue 类
Queue 类和 Stack 类非常相似,只是添加元素和移除元素的原则不同。
- 向队列添加元素
- 从队列移除元素
- 查看队列头元素
- 检查队列是否为空
- 获取队列的长度
- 清空队列
- 创建 toString 方法
/*** 创建队列*/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];}// 如果队列中没有元素就返回 true,否则返回 falseisEmpty() {return this.count - this.lowestCount === 0;// return this.size();}// 返回队列的元素个数size() {return this.count - this.lowestCount;}// 清空队列clear() {this.items = {};this.count = 0;this.lowestCount = 0;}// toString 方法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()); // truequeue.enqueue('John');queue.enqueue('Jack');console.log(queue.toString()); // John, Jackconsole.log(queue.size()); // 2console.log(queue.isEmpty()); // falsequeue.dequeue();console.log(queue.peek()); // Jack
双端队列数据结构
双端队列(Deque,double-ended queue)是一种允许我们同时从前端和后端添加和移除元素的特殊队列。
在计算机科学中,双端队列的一个常见应用是存储一些列的撤销操作。每当用户在软件中进行了一个操作,改操作会被存储在一个双端队列中。每当用户点击撤销按钮时,该操作会被从双端队列中弹出,表示它被从后面移除了。在进行了预定义的一定数量的操作后,最先进行的操作会被双端队列的前端移除。
创建 Deque 类
/*** 创建双端队列(double-ended queue)*/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.lowestCount = 0;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];}// 如果队列中没有元素就返回 true,否则返回 falseisEmpty() {return this.count - this.lowestCount === 0;// return this.size();}// 返回队列的元素个数size() {return this.count - this.lowestCount;}// 清空队列clear() {this.items = {};this.count = 0;this.lowestCount = 0;}// toString 方法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()); // truedeque.addBack('John');deque.addBack('Jack');deque.addFront('Camila');console.log(deque.toString()); // Camila, John, Jackconsole.log(deque.size()); // 3console.log(deque.isEmpty()); // falsedeque.removeFront();console.log(deque.toString()); // John, Jackconsole.log(deque.peekFront()); // Johndeque.removeBack();console.log(deque.peekBack()); // Johndeque.clear();
使用队列和双端队列来解决问题
击鼓传花游戏
击鼓传花游戏:
- 孩子们围城一个圆圈,把花尽快地传递给傍边的人。
- 某一时刻传花停止,这个时候花在谁手里,谁就退出圆圈、结束游戏。
- 重复这个过程,直到只剩一个孩子(胜者)。
使用队列
function hotPotato(elementList, num) {const queue = new Queue();const elimitatedList = [];for (let i = 0; i < elementList.length; i++) {queue.enqueue(elementList[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 = hotPotato(names, 7);result.eliminated.forEach(name => {console.log(`${name}在击鼓传花游戏中被淘汰`);})console.log(`胜利者:${result.winner}`);/*Camila在击鼓传花游戏中被淘汰Jack在击鼓传花游戏中被淘汰Carl在击鼓传花游戏中被淘汰Ingrid在击鼓传花游戏中被淘汰胜利者:John*/
回文检查器
回文检查器:
- 回文是正反都能读通的单词、词组、数或一系列字符的序列,例如:madam 或 racecar。
- 最简单的检查方式:将字符串反向排列并检查它和原字符串是否相同。
使用双端队列
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;let 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')); // a trueconsole.log('level', palindromeChecker('level')); // level trueconsole.log('JavaScript', palindromeChecker('JavaScript')); // JavaScript false
JavaScript 任务队列
当我们在浏览器中打开新的标签时,就会创建一个任务队列。这是因为每个标签都是单线程处理所有的任务,称为事件循环。
浏览器要负责多个任务,如渲染 HTML、执行 JavaScript 代码、处理用户交互(用户输入、鼠标点击等)、执行和处理异步请求。
