队列数据结构
队列是遵循先进先出(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,否则返回 false
isEmpty() {
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()); // true
queue.enqueue('John');
queue.enqueue('Jack');
console.log(queue.toString()); // John, Jack
console.log(queue.size()); // 2
console.log(queue.isEmpty()); // false
queue.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,否则返回 false
isEmpty() {
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()); // true
deque.addBack('John');
deque.addBack('Jack');
deque.addFront('Camila');
console.log(deque.toString()); // Camila, John, Jack
console.log(deque.size()); // 3
console.log(deque.isEmpty()); // false
deque.removeFront();
console.log(deque.toString()); // John, Jack
console.log(deque.peekFront()); // John
deque.removeBack();
console.log(deque.peekBack()); // John
deque.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 true
console.log('level', palindromeChecker('level')); // level true
console.log('JavaScript', palindromeChecker('JavaScript')); // JavaScript false
JavaScript 任务队列
当我们在浏览器中打开新的标签时,就会创建一个任务队列。这是因为每个标签都是单线程处理所有的任务,称为事件循环。
浏览器要负责多个任务,如渲染 HTML、执行 JavaScript 代码、处理用户交互(用户输入、鼠标点击等)、执行和处理异步请求。