前言
嘿,掘友们!今天我们来了解并实现数据结构 —— 队列和双端队列。
本文主要内容
- 队列
- 双端队列
- 击鼓传花模拟循环队列
- 用双端队列做回文检查
队列
队列是遵循先进先出(FIFO)原则的一组有序的项。队列在尾部添加新元素,从顶部移除元素。
在现实中,最常见的队列的例子就是排队,排在第一位的人会先接受服务。
在计算中,常见的例子就是打印队列。比如打印五份文档。每个文档都会发送至打印队列中,第一个发送到打印队列的文档会首先被打印,直到打印完所有文档。
队列实现
class Queue {
constructor() {
this.count = 0
this.lowerCount = 0
this.items = {}
}
// 从后面添加
enqueue(element) {
this.items[this.count] = element
this.count++
}
// 从前面弹出
dequeue() {
if(this.isEmpty()) return undefined
const result = this.items[this.lowerCount]
this.lowerCount++
return result
}
isEmpty() {
return this.size() === 0
}
size() {
return this.count - this.lowerCount
}
peek() {
return this.items[this.lowerCount]
}
clear() {
this.count = 0
this.lowerCount = 0
this.items = {}
}
print() {
if(this.isEmpty()) return undefined
let string = `${this.items[this.lowerCount]}`
for(let i = this.lowerCount + 1; i < this.count; i++) {
string = `${string}, ${this.items[i]}`
}
return string
}
}
双端队列
双端队列是一种允许我们同时从前端和后端添加和移除元素的特殊队列。
在现实生活中,一个刚买了票的人如果只是还需要询问一些简单的问题,就可以直接回到队伍的头部。另外,在队伍末尾的人如果赶时间,可以直接离开队伍。
在计算机中,双端队列的一个常见应用是存储一系列的撤销操作。每当用户在软件中进行了一个操作,该操作就会存在一个双端队列中。当用户点击撤销按钮时,该操作会被从双端队列中弹出,表示他被从后面移除了,在进行了预先定义的一定数量的操作后,最先进行的操作会被从双端队列的前端移除。
双端队列同时遵守了先进先出和后进先出的原则,它是把队列和栈相结合的一种数据结构。
双端队列实现
isEmpty、peek、size、clear、print和队列的操作相同。
class Deque {
constructor() {
this.count = 0
this.lowerCount = 0
this.items = {}
}
isEmpty() {
return this.size() === 0
}
size() {
return this.count - this.lowerCount
}
peek() {
return this.items[this.lowerCount]
}
clear() {
this.count = 0
this.lowerCount = 0
this.items = {}
}
print() {
if(this.isEmpty()) return undefined
let string = `${this.items[this.lowerCount]}`
for(let i = this.lowerCount + 1; i < this.count; i++) {
string = `${string}, ${this.items[i]}`
}
return string
}
// 双端队列前端添加元素
addFront(element) {
if(this.isEmpty()) {
this.addBack(element)
} else if(this.lowerCount > 0) {
this.lowerCount--
this.items[this.lowerCount] = element
} else {
for(let i = this.count; i > 0; i++) {
this.items[i] = this.items[i-1]
}
this.count++
this.lowerCount = 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.lowerCount]
this.lowerCount++
return result
}
// 双端队列后端移除元素
removeBack() {
if(this.isEmpty()) return undefined
this.count--
const result = this.items[this.count]
return result
}
// 双端队列前端的第一个元素
peekFront() {
return this.items[this.lowerCount]
}
// 双端队列后端的第一个元素
peekBack() {
return this.items[this.count-1]
}
}
击鼓传花模拟循环队列
function hotPotato(list, num) {
const queue = new Queue()
const arr = []
for(let i = 0; i < list.length; i++) {
queue.enqueue(list[i])
}
while(queue.size() > 1) {
for(let i = 0; i < num; i++) {
queue.enqueue(queue.dequeue())
}
arr.push(queue.dequeue())
}
return {
list: arr,
winner: queue.dequeue()
}
}
测验
const name = ['John', 'Jack', 'Camila', 'Ingrid', 'Carl']
const result = hotPotato(name, 7)
result.list.forEach(name => {
console.log(`${name}在击鼓窗花游戏中被淘汰`)
})
console.log(`胜利者: ${result.winner}`)
输出
Camila在击鼓窗花游戏中被淘汰
Jack在击鼓窗花游戏中被淘汰
Carl在击鼓窗花游戏中被淘汰
Ingrid在击鼓窗花游戏中被淘汰
胜利者: John
用双端队列做回文检查
function palindromeChecker(string) {
if(string === undefined || string === null || (string !==null && string.length ===0)) return false
const deque = new Deque()
const lowerString = string.toLocaleLowerCase().split(' ').join('')
let isEqual = true
let firstChar, lastChar
for(let i = 0; i < string.length; i++) {
deque.addBack(lowerString.charAt(i))
}
while(deque.size() > 1 && isEqual) {
firstChar = deque.removeFront()
lastChar = deque.removeBack()
console.log(firstChar, lastChar);
isEqual = firstChar === lastChar ? true : false
}
return isEqual
}
测验
console.log('aa', palindromeChecker('aa'));
console.log('aA', palindromeChecker('aA'));
console.log('a', palindromeChecker('a'));
console.log('abc', palindromeChecker('abc'));
输出
aa true
aA true
a true
abc false