队列是遵循先进先出(FIFO,也称为先来先服务)原则的一组有序的项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。
首先需要一个用于存储队列中元素的数据结构。可以使用数组,但是为了写出一个在获取元素时更高效的数据结构,将使用一个对象来存储元素。会发现Queue类和Stack类非常类似,只是添加和移除元素的原则不同。
export default class Queue {
constructor() {
this.count = 0;
this.lowestCount = 0;
this.items = {
};
}
// enqueue(element(s)): 向队列尾部添加一个或多个新的项
enqueue(element) {
this.items[this.count] = element
this.count++
}
// dequeue(): 移除队列的第一项(即排在队列最前面的项)并返回被移除的元素
dequeue() {
if(this.isEmpty()) {
return undefined
}
const result = this.items[this.lowestCount]
delete this.items[this.lowestCount]
this.lowestCount++
return result
}
// peek(): 返回队列中第一个元素--最先被添加,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息)
peek() {
if(this.isEmpty()) {
return undefined
}
return this.items[this.lowestCount]
}
// isEmpty(): 如果队列中不包含任何元素,返回true,否则返回false
isEmpty() {
return this.count - this.lowestCount === 0
}
// size(): 返回队列包含的元素个数,与数组的length属性类似
size() {
return this.count - this.lowestCount
}
// 清理队列
clear() {
this.count = 0;
this.lowestCount = 0
this.items = {
}
}
// 创建一个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')
queue.enqueue('ytllll')
console.log(queue.toString()) //John, Jack, ytllll
queue.enqueue("Kotoko")
console.log(queue.toString()) //John, Jack, ytllll, Kotoko
console.log(queue.size()) //4
console.log(queue.isEmpty()) //false
// 删除前两个元素
queue.dequeue()
queue.dequeue()
console.log(queue.toString()) //ytllll, Kotoko
双端队列(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) {
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]
}
// isEmpty(): 如果队列中不包含任何元素,返回true,否则返回false
isEmpty() {
return this.count - this.lowestCount === 0
}
// size(): 返回队列包含的元素个数,与数组的length属性类似
size() {
return this.count - this.lowestCount
}
// 清理队列
clear() {
this.count = 0;
this.lowestCount = 0
this.items = {
}
}
// 创建一个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')
console.log(deque.toString()) //John, Jack
// 向双端队列前端加入两个元素
deque.addFront('ytllll')
deque.addFront('Kotoko')
console.log(deque.toString()) //Kotoko, ytllll, John, Jack
console.log(deque.size()) //4
console.log(deque.isEmpty()) //false
// 取队列的第一个元素
console.log(deque.peekFront()) //Kotoko
// 取队列的最后一个元素
console.log(deque.peekBack()) //Jack
// 移除队列第一个元素
deque.removeFront()
console.log(deque.toString()) //ytllll, John, Jack
// 移除队列最后一个元素
deque.removeBack()
console.log(deque.toString()) //ytllll, John
在这个游戏中,孩子们围成一个圆圈,把花尽快的传递给旁边的人。某一时刻传花停止,这个时候花在谁手里,谁就退出圆圈,结束游戏。重复这个过程,直到只剩下一个孩子(胜者)。
接下来使用队列来实现这个游戏:
function hotPotato(elementList, num) {
// 定义一个空队列
const queue = new Queue()
// 定义一个保存淘汰者的数组
const eliminatedList = []
for(let i = 0; i < elementsList.length; i++) {
// 遍历传入的elementsList数组,逐个传入queue队列
queue.enqueue(elementsList[i])
}
while(queue.size() > 1) {
for(let i = 0; i < num; i++) {
// 从队列头开始,移除一项再将返回值添加会队列,模拟一次传递
queue.enqueue(queue.dequeue)
}
// 循环结束,传到第num次的人淘汰,并加入数组
eliminatedList.push(queue.dequeue)
}
return {
eliminated: eliminatedList,
// 队列中剩下的最后一个人就是胜者
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, 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('Step on no pets', palindromeChecker('Step on no pets'));
以上输出都是true