队列 - 双端队列实现
之前实现的单端队列, 只能从队列的尾部进, 头部出.
但现在我们来实现一种从两端都可进行出队入队的结构, 即双端队列 deque
.
在计算机中, 双端队列最常用的一个场景是存储一系列的撤销操作. 当然用户点击了某个操作, 则此操作会被存在一个双端队列中, 类似栈里.
当用户点击撤销操作时, 该操作会被从双端队列弹出. 一顿猛如虎的操作后, 最先进行的操作会先进行出队. 由于双端队列 同时遵循先进先出和后进先出
原则, 可知它是一种 队列+栈
结合的数据结构.
以数组来模拟队列直观演示一下,
当单端时是这样的, 假设左边是队首, 右边是队尾, 是单向流动的.
// 初始队列
var queue = [ ]
// 元素 1, 2, 3 依次从队尾入队
[1, 2, 3]
// 队首元素出队
[2, 3]
当双端队列时候, 队尾和队首都可进行新增和删除元素, 还是以数组来模拟, 左边是队首, 右边是对尾.
// 初始队列
var deque = []
// 元素1, 2 从队首或者队尾入对
[1, 2]
// 元素3 从队首入队
[3, 1, 2]
// 元素4 从队尾入队
[3, 1, 2, 4]
// 队首元素出队一次
[1, 2, 4]
// 队尾元素出队一次
[1, 2]
创建队列
同之前一样, 底层用 js对象来实现, 或者直接在原来的基础改造即可.
class Deque {
constructor() {
this.count = 0
this.frontIndex = 0
this.obj = {}
}
// 队列长度
size() {
return this.count - this.frontIndex
}
// 队列是否为空
isEmpty() {
this.count - this.frontIndex == 0
}
// 清空队列
clear() {
this.obj = {}
this.frontIndex = 0
this.count = 0
}
// 查看全队列
toString() {
if (this.isEmpty()) return this.obj
let objString = `${this.obj[this.frontIndex]}`
for (let i = this.frontIndex + 1; i < this.count; i++) {
objString = `${objString}, ${this.obj[i]}`
}
return objString
}
}
双端队列除了以上通用方法外, 还运行在两端添加和移除元素, 因此还有又如下方法:
- addFront () 在双端队列前端添加元素
- addBack () 在双端队列后端添加元素, 类似单端的 enqueue
- removeFront () 从双端队列前端移除第一个元素, 类似单端的 dequeue
- removeBack () 从双端队列后端移除第一个元素, 类似单端的 pop
- peekFront () 从双端队列前端返回第一个元素, 类似单端的 peek
- peekBack () 从双端队列后端返回第一个元素
队尾添加元素
class Deque {
constructor() {
this.count = 0
this.frontIndex = 0
this.obj = {}
}
// 队尾添加元素
addBack(item) {
this.obj[this.count] = item
this.count++
}
}
队尾删除元素
class Deque {
constructor() {
this.count = 0
this.frontIndex = 0
this.obj = {}
}
// 队尾删除元素
removeBack() {
if (this.isEmpty()) return undefined
this.count -= 1
let backItem = this.obj[this.count]
delete this.obj[this.count]
return backItem
}
}
队首添加元素
class Deque {
constructor() {
this.count = 0
this.frontIndex = 0
this.obj = {}
}
// 队首添加元素
addFront(item) {
// 当为空队列时, 则从队尾加
if (this.isEmpty()) {
this.addBack(item)
} else if (this.frontIndex > 0) {
// 当队首元素已有过出队时, 此时 frontIndex > 0, 此时进行 减1替换即可
this.frontIndex -= 1
this.obj[this.frontIndex] = item
} else {
// 当队首元素未有过出队时, 此时 frontIndex = 0, 后面元素依次后挪
for (let i = this.count; i > 0; i--) {
this.obj[i] = this.obj[i-1]
}
// 移完后将队首元素的值和索引都更新, 队列长度加1
this.obj[0] = item
this.frontIndex = 0
this.count += 1
}
}
}
队首删除元素
class Deque {
constructor() {
this.count = 0
this.frontIndex = 0
this.obj = {}
}
// 队尾删除元素
removeBack() {
if (this.isEmpty()) return undefined
this.count -= 1
let backItem = this.obj[this.count]
delete this.obj[this.count]
return backItem
}
}
于是, 这样一个双端都可以进行出队, 入队的结构就做好啦!
测试验证
然后整体来测试一波.
class Deque {
constructor() {
this.count = 0
this.frontIndex = 0
this.obj = {}
}
// 队列长度
size() {
return this.count - this.frontIndex
}
// 队列是否为空
isEmpty() {
if (this.count - this.frontIndex == 0) return true;
}
// 清空队列
clear() {
this.obj = {}
this.frontIndex = 0
this.count = 0
}
// 查看全队列
toString() {
if (this.isEmpty()) return this.obj;
let objString = `${this.obj[this.frontIndex]}`
for (let i = this.frontIndex + 1; i < this.count; i++) {
objString = `${objString}, ${this.obj[i]}`
}
return objString
}
// 队尾添加元素
addBack(item) {
this.obj[this.count] = item
this.count++
}
// 队尾删除元素
removeBack() {
if (this.isEmpty()) return undefined
this.count -= 1
let backItem = this.obj[this.count]
delete this.obj[this.count]
return backItem
}
// 队首添加元素
addFront(item) {
// 当为空队列时, 则从队尾加
if (this.isEmpty()) {
this.addBack(item)
} else if (this.frontIndex > 0) {
// 当队首元素已有过出队时, 此时 frontIndex > 0, 此时进行 减1替换即可
this.frontIndex -= 1
this.obj[this.frontIndex] = item
} else {
// 当队首元素未有过出队时, 此时 frontIndex = 0, 后面元素依次后挪
for (let i = this.count; i > 0; i--) {
this.obj[i] = this.obj[i-1]
}
// 移完后将队首元素的值和索引都更新, 队列长度加1
this.obj[0] = item
this.frontIndex = 0
this.count += 1
}
}
// 队首删除元素
removeFront() {
if (this.isEmpty()) return undefined
let frontItem = this.obj[this.frontIndex]
delete this.obj[this.frontIndex]
this.frontIndex += 1
return frontItem
}
}
// test
// 初始队列
let deque = new Deque()
console.log('新建空队列此时为: ', deque.toString());
let item0 = deque.removeBack()
console.log('此时队列为: ', deque.toString());
console.log('元素1, 2 分别从队首, 对尾入队');
deque.addFront(1)
deque.addBack(2)
console.log('此时队列为: ', deque.toString());
console.log('元素3 从队首入队');
deque.addFront(3)
console.log('此时队列为: ', deque.toString());
console.log('元素4 从队尾入队');
deque.addBack(4)
console.log('此时队列为: ', deque.toString());
let item1 = deque.removeFront()
console.log('队首元素出队一次, 删除的元素是: ', item1);
console.log('此时队列为: ', deque.toString());
let item2 = deque.removeBack()
console.log('队尾元素出队一次, 删除的元素是: ', item2);
console.log('此时队列为: ', deque.toString());
PS F:\algorithms> node .\deque.js
新建空队列此时为: {}
此时队列为: {}
元素1, 2 分别从队首, 对尾入队
此时队列为: 1, 2
元素3 从队首入队
此时队列为: 3, 1, 2
元素4 从队尾入队
此时队列为: 3, 1, 2, 4
队首元素出队一次, 删除的元素是: 3
此时队列为: 1, 2, 4
队尾元素出队一次, 删除的元素是: 4
此时队列为: 1, 2
只要逻辑理解了, 然后实现就是慢慢思考即可, 还是比较有趣的.
耐心和恒心, 总会获得回报的.