1. 队列是是什么?
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。
队尾:进行插入操作的端
队头:进行删除操作的端
空队列:队列中没有元素时
队列的数据元素又称为队列元素。
入队:在队列中插入一个队列元素
出队:从队列中删除一个队列元素
因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO - first in first out)线性表。


2. 队列结构的封装
第一种:与栈结构的封装类似
js
class Queue {
// constructor(){
// this.items = [];
// }
// items = []; // items 可以放在 constructor中,也可以直接提到外面来
// _items = []; // 给items加下划线_,表示是私有的。君子协定,大家公认的,但其实外部依旧可以访问修改的。
#items = []; // es13 出现 js 默认支持的私有属性,加井号 # 表示私有属性。
// 出队
dequeue(){
// 用起来方便,但JS处理起来是 性能不高,效率低下 的(因为JS需要将队头弹出,然后逐步将剩余元素往前挪)。对于大数据就更...
this.#items.shift();
// 不符合 "好的数据结构是为了提高效率的" 的原则
// 优化看后续代码
}
// 入队
enqueue(data){
this.#items.push(data);
}
// 返回队头
front(){
return this.#items.at(0);
}
isEmpty(){
return this.#items.length === 0;
}
size(){
return this.#items.length;
}
clear(){
this.#items = []
}
toString(){
return this.#items.join(" ")
}
}
let queue = new Queue()

出队尝试一:使用 delete 方法 (此方法并不可行,因为数组会变得丑陋)

出队尝试二:使用对象结构(可行,结构好看,空间占用小,性能更好)

js
class Queue {
#items = {}
#lowCount = 0 // 记录队头索引值
#count = 0 // 插入数量 (size = lowCount - count) 思路与数据库索引其实是一致的
// 出队
dequeue(){
// 假如先进行删除,要保证没有造成影响
if(this.isEmpty()){
// return undefined
return
}
let res = this.#items[this.#lowCount]
delete this.#items[this.#lowCount]
this.#lowCount++
return res
}
// 入队
enqueue(data){
this.#items[this.#count] = data
this.#count++
}
// 返回队头
front(){
return this.#items[this.#lowCount]
}
isEmpty(){
return this.size() === 0
}
size(){
return this.#count - this.#lowCount
}
clear(){
this.#items = {}
this.#count = 0
this.#lowCount = 0
}
toString(){
let str = ""
for(let i=this.#lowCount;i<this.#count;i++){
// 注意这里拼接时,最后会多一个空格,要处理一下
str+=`${this.#items[i]} `
}
return str
}
}
let queue = new Queue()


3. 队列应用之击鼓传花
(经典面试题哦~)
设计一个函数完成击鼓传花游戏。既定的人,设定的敲鼓次数。一次敲鼓,传一个人(第一次敲鼓:user1-> user2),鼓数敲完,传到的人出局失败,剩余一个人为胜利者。
js
function game(list,num) {
let queue = new Queue()
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())
}
console.log(queue.dequeue(),"淘汰了")
}
return queue.dequeue()
}
game(["user1","user2","user3","user4","user5"],7)
4. 双端队列
例如排队的时候,前面的人可以出列也可能有人插队到队头,队尾有人入队,也可能有人等太久了直接出队。
再比如进行 ctrl+z 撤销的时候的逻辑:设定了保存100步,在第101步时,第一步出队。在100步时使用了 ctrl+z 撤销了一步,第100步出队了~
js
// double-ended queue
class DeQueue {
#items = {}
#lowCount = 0 // 记录队头索引值
#count = 0 // 插入数量 (size = lowCount - count) 思路与数据库索引其实是一致的
// 出队(默认:可以从队头进行删除)
removeFront(){
// 假如先进行删除,要保证没有造成影响
if(this.isEmpty()){
// return undefined
return
}
let res = this.#items[this.#lowCount]
delete this.#items[this.#lowCount]
this.#lowCount++
return res
}
// 入队(默认:可以从队尾进行增加)
addBack(data){
this.#items[this.#count] = data
this.#count++
}
// 双端队列新增:可以从队头增加
addFront(data){
// 要考虑3个场景
// 1. 如果为空,复用 addBack
if(this.isEmpty()){
this.addBack(data)
} else {
// 2. lowCount > 0, 就直接将 lowCount-1, 再将新元素放到 lowCount-1 的位置上
if(this.#lowCount > 0){
this.#lowCount--;
this.#items[this.#lowCount] = data
} else {
// 3. lowCount === 0
// 可以引出链表的好处,在任意位置插入删除元素。
// 现在只能硬着头皮干,使用与JS数组unshift一样的机制(性能不太好)
// 从队头插入一个元素,之前的每一位向后挪一步
for(let i=this.#count;i>0;i--){
this.#items[i] = this.#items[i-1]
}
this.#items[0] = data
this.#count++
}
}
}
// 双端队列新增:可以从队尾删除
removeBack(){
if(this.isEmpty()){
return
}
this.#count--
const res = this.#items[this.#count]
delete this.#items[this.#count]
return res
}
// 返回队头元素
peekFront(){
return this.#items[this.#lowCount]
}
// 双端队列新增:返回队尾元素
peekBack(){
// 可加可不加,不影响
// if(this.isEmpty()){
// return
// }
return this.#items[this.#count-1]
}
isEmpty(){
return this.size() === 0
}
size(){
return this.#count - this.#lowCount
}
clear(){
this.#items = {}
this.#count = 0
this.#lowCount = 0
}
toString(){
let str = ""
for(let i=this.#lowCount;i<this.#count;i++){
// 注意这里拼接时,最后会多一个空格,要处理一下
str+=`${this.#items[i]} `
}
return str
}
}
let dequeue = new DeQueue()
addFront场景3:当 lowCount === 0 时的测试

addFront场景2:当 lowCount > 0, 就直接将 lowCount-1, 再将新元素放到 lowCount-1 的位置上 的测试

addFront场景1:当 dequeue 为空时的测试

removeBack 测试

peekBack、peekFront 测试


5. 双端队列的应用
- 回文检查(仅笔试应用...,在业务场景中用的不多)
例如(大小写不计,空格不计):dad、Dad、Da d、假似真时真似假、
js
function test(str){
const lowstr = str.toLocaleLowerCase().split(" ").join("")
let dequeue = new DeQueue()
for(let i=0;i<lowstr.length;i++){
dequeue.addBack(lowstr[i])
}
let isEqual = true
while(dequeue.size()>1){
if(dequeue.removeFront() !== dequeue.removeBack()){
isEqual = false
break;
}
}
return isEqual
}
test("D a d")
