队列和双端队列
本篇博客的所有代码都整理到自己的github库中,以便大家查看及学习。
本篇博客主要是从以下几点展开
- 队列数据结构
- 双端数列数据结构
- 向队列和双端队列增加元素
- 从队列和双端队列中删除元素
- 经典算法
1. 队列数据结构
队列遵循先进先出(FIFO)原则的一组有序的项。队列在尾部添加元素,在头部移除元素,最先添加的元素一定是放在队列的末尾。
队列的例子在生活中很常见,在排队买票,排队上车等等
2. 创建队列
接下来我们通过创建自己的类表示队列,然后声明以下方法
enqueue(elements)
: 向尾部追加元素(追加新的项)dequeue()
: 移除队列中的第一项,并返回移除的元素peek()
: 返回队列中的第一项(并不会对该项有任何操作)isEmpty()
: 判断队列是否为空size()
: 返回队列中包含的元素的个数clear()
: 清空队列toString()
: 转为字符串的方法
class Queue{
constructor(){
this.items = {} // 根据上篇文章《栈》的理解,这次我们也是用数组的方式来放队列中的各元素
this.count = 0 // 队列中各项元素的个数
this.lowestCount = 0 // 用来帮助我们追踪第一个元素的变量
}
enqueue(elem){
this.items[this.count] = elem //将队列中的最后一个位置设置为追加进来的元素
this.count++
}
dequeue(){
if (this.isEmpty()) {
return ''
}
const result = this.items[this.lowestCount]
delete this.items[this.lowestCount]
this.lowestCount++
return result
}
peek(){
if (this.isEmpty()) {
return ''
}
return this.items[this.lowestCount]
}
isEmpty(){
return this.size() === 0
}
size(){
return this.count - this.lowestCount
}
clear(){
this.items = {}
this.count = 0
this.lowestCount = 0
}
toString(){
if (this.isEmpty()) {
return ''
}
let result = this.items[this.lowestCount]
for (const k in this.items) {
if (this.items[k] && this.items[k] !== this.items[this.lowestCount]) {
result = `${result},${this.items[k]}`
}
}
return result
}
}
var queue = new Queue()
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
queue.enqueue(4)
console.log(queue) // Queue { items: [ 1, 2, 3, 4 ], count: 4, lowestCount: 0 }
console.log(queue.size()) // 4
console.log(queue.isEmpty()) // false
console.log(queue.dequeue()) // 1
console.log(queue) // Queue { items: [ <1 empty item>, 2, 3, 4 ], count: 4, lowestCount: 1 }
console.log(queue.size())
console.log(queue.peek()) // 2
console.log(queue) // Queue { items: [ <1 empty item>, 2, 3, 4 ], count: 4, lowestCount: 1 }
console.log(queue.toString()) // 2,3,4
3. 双端队列数据结构
双端队列(deque,或称 double-end queue) 是一种允许我们同时从前端和后端添加和移除元素的特殊队列。
最常见的生活中的例子就是大家排队买票,当第一个人买完票后,可能会再次返回问售票员一些问题,这个时候他就不用去排队,而排在队尾的人可能就因为赶时间,提前离开队伍。
双端队列最常见的一个应用就是存储一系列撤销操作
4. 创建 Deque 类
和之前一样,我们也是在构造函数中声明一些双端队列内部的属性及方法,isEmpty
,clear
,size
,toString
。
接下来定义一些对外的方法
addFront(element)
: 向双端队列前面插入新的元素addBack(element)
: 向双端队列后面追加新的元素removeFront()
: 移除双端队列前面的元素,并返回移除的元素removeBack()
: 移除双端队列后面的元素,并返回移除的元素peekFront()
: 返回双端队列中的第一项元素peekBack()
: 返回双端队列中的最后一项元素
class Deque{
constructor(){
this.items = {}
this.count = 0
this.lowestCount = 0
}
isEmpty(){
return this.count === 0
}
clear(){
this.items = {}
this.count = 0
this.lowestCount = 0
}
size(){
return this.count - this.lowestCount
}
addFront(elem){
if (this.isEmpty()) {
this.addBack(elem)
} else if (this.lowestCount > 0) {
this.lowestCount --
this.items[this.lowestCount] = elem
} else {
for (let i = this.count; i > 0; i--) {
this.items[i] = this.items[i - 1]
}
this.count ++
this.lowestCount = 0
this.items[0] = elem
}
}
addBack(elem){
this.items[this.count] = elem
this.count++
}
removeFront(){
const result = this.items[this.lowestCount]
this.items[this.lowestCount] = undefined
this.lowestCount ++
return result
}
removeBack(){
const result = this.items[this.count]
this.count --
return result
}
peekFront(){
return this.items[this.lowestCount]
}
peekBack(){
return this.items[this.count]
}
toString(){
if (this.isEmpty()) {
return ''
}
let result = this.items[this.lowestCount]
for (const k in this.items) {
if (this.items[k] !== result && this.items[k]) {
result = `${result},${this.items[k]}`
}
}
return result
}
}
var deque = new Deque()
deque.addBack(1)
deque.addFront(0)
deque.addBack('Alice')
deque.addFront('JD')
console.log(deque) //Deque {items: { '0': 'JD', '1': 0, '2': 1, '3': 'Alice' },count: 4,lowestCount: 0 }
console.log(deque.removeFront()) // JD
console.log(deque) //Deque {items: { '0': undefined, '1': 0, '2': 1, '3': 'Alice' },count: 4, lowestCount: 1}
console.log(deque.size()) // 3
console.log(deque.toString()) // 0,1,Alice
5. 经典算法
1. 击鼓传花
在思考这个题目的时候我们先了解一下循环队列。而循环队列最常见的例子就是击鼓传花。击鼓传花就是一群孩子们围成圈,将花传递给旁边的人,当在某一时刻,传花停止时,花在谁的手里谁就会被淘汰。重复这个过程,直到剩下最后一个孩子
function hotPotato(elementList,num){
const queue = new Queue()
const eliminatedList = [] // 淘汰的元素
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())
}
eliminatedList.push(queue.dequeue())
}
return {
eliminated:eliminatedList,
winner:queue.dequeue()
}
}
var names = ['John','Jack','Calmia','Alice','Carl']
const result = hotPotato(names,7)
// console.log(result) // { eliminated: [ 'Calmia', 'Jack', 'Carl', 'Alice' ], winner: 'John' }
result.eliminated.forEach(name => {
console.log(`${name}在游戏中被淘汰`)
})
console.log(`胜利者是:${result.winner}`)
// Calmia在游戏中被淘汰
// Jack在游戏中被淘汰
// Carl在游戏中被淘汰
// Alice在游戏中被淘汰
// 胜利者是:John
上述的代码不好理解,这里画一幅图,便于理解;图中只表示了第一次循环的状态
2. 回文检查器
最简单的方式就是将字符串反向排列后,检查它和原字符串是否一样,若相同,则为一个回文字符串
function palindromeChecker(string) {
if (!string) { // 判空
return false
}
let deque = new Deque()
const str = string.toLowerCase() // 转为了小写
const tempStr = str.split(' ').join() // 去除字符串中的空格
let isEqual = true
let firstChar; let lastChar
// 将字符串中的第一个字符串放到最后
for (let i = 0; i < tempStr.length; i++) {
deque.addBack(tempStr.charAt(i))
}
// 比较每次双端队列移除的项是否相等,相等则继续循环,直到size为1,不想等则直接退出循环
while (deque.size() > 1 && isEqual) {
firstChar = deque.removeFront()
lastChar = deque.removeBack()
if (firstChar !== lastChar) {
isEqual = false
}
}
return isEqual
}
本文来自博客园,作者:前端加油站,转载请注明原文链接:https://www.cnblogs.com/bllx/p/15949239.html