数据结构与算法-队列
《学习JavaScript数据结构与算法(第3版)》
队列是遵循先进先出(FIFO,也称为先来先服务)原则的一组有序的项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。
队列数据结构
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;
}
}
双端队列
双端队列(deque
,或称double-ended queue
)是一种允许我们同时从前端和后端添加和移除元素的特殊队列。
同时,遵循了先进先出和后进先出原则。
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) {
// 如果双端队列删除过前端元素,只需要lowestCount属性减1并将新的值放在这个键的位置上
this.lowestCount--;
this.items[this.lowestCount] = element;
} else {
// lowestCount为0
// 方法一:设置一个负值键,同时更新用于计算双端队列长度的逻辑,使其也包含负值。这种情况下,添加一个新元素的操作仍然能保持最低的计算成本。
// 方法二:类似数组,将所有元素后移一位来空出第一个位置。然后将新元素覆盖第一个位置。如下。
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;
}
}
用队列解决问题
击鼓传花游戏——循环列表
n个同学坐着围成一个圆圈,指定一个同学手里拿着一束花,每个同学都需要将花传给下一个同学,将花传递m次,得到花的人出局,然后再将花向下传递m次,得到花的人出局,以此类推,最后剩下的是哪位同学?(传递过程中不考虑已经出局的人)
在这个游戏中,孩子们围成一个圆圈,把花尽快地传递给旁边的人。某一时刻传花停止,这个时候花在谁手里,谁就退出圆圈、结束游戏。重复这个过程,直到只剩一个孩子(胜者)
import Queue from '../queue';
export function hotPotato(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 = hotPotato(names, 7);
result.eliminated.forEach(name => {
console.log(`${name}在击鼓传花游戏中淘汰。`);
});
console.log(`胜利者:${result.winner}`);
检查短语是否为回文——回文检查器, 双端队列
回文是正反都能读通的单词、词组、数或一系列字符的序列,例如
madam
或racecar
。
有不同的算法可以检查一个词组或字符串是否为回文。最简单的方式是将字符串反向排列并检查它和原字符串是否相同。如果两者相同,那么它就是一个回文。我们也可以用栈来完成,但是利用数据结构来解决这个问题的最简单方法是使用双端队列。
import Deque from '../deque';
export 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 firstChar;
let lastChar;
let isEqual = true;
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;
}