这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战
介绍
双端队列是一种将栈的原则和队列的原则混合在一起的数据结构,即头部和尾部都可以添加或删除。本期我们将用js普通对象不借助数组,去实现一个双端队列类,以便加深对此数据结构的认识。
概念
双端队列(deque,或称double-ended queue)是一种允许我们同时从前端和后端添加和移除元素的特殊队列。它同时遵守了先进先出和后进先出原则,也可以说它是把队列和栈相结合的数据结构。
正文
1.基础结构
我们先看一下,要实现的大体结构与其方法:
const items = new WeakMap();
const count = new WeakMap();
const delCount = new WeakMap();
class Deque {
constructor() {
items.set(this, {})
count.set(this, 0)
delCount.set(this, 0)
return this;
}
addBefore(item) {}
addAfter(item) {}
peekBefore() {}
peekAfter() {}
removeBefore() {}
removeAfter() {}
clear() {
items.set(this, {});
count.set(this, 0);
delCount.set(this, 0);
}
get() {
return {
...items.get(this),
length: this.size()
}
}
size() {
return count.get(this) - delCount.get(this);
}
}
export default Deque;
我们使用WeakMap这个ES6新数据结构来完成队列的私有变量了,保护其内部的参数不被外界破坏。我们在这里创建三个私有变量分别为items元素存储器与count计数器还有delCount删除收集计数器,至于后面的几个方法为:
- size:返回当前队列的元素个数。
- get:返回一个当期队列所有元素和长度值。
- clear:清除队列所有的元素。
- addBefore&addAfter:分别在前后注入一个元素,并返回当前队列的长度。
- peekBefore&peekAfter:分别在最前和最后获取元素,并返回这个元素的值。
- removeBefore&removeAfter:分别在最前和最后抹除元素,并返回这个元素的值。
其中size,clear,get方法与上一期的手撸队列完全一致这里不做赘述,接下来我们先看一下具体实现吧。
2.addBefore&addAfter
addBefore(item) {
let _delCount = delCount.get(this);
let _items = items.get(this)
let _count = count.get(this);
if (this.size() === 0) {
this.addAfter(item);
}
else if (_delCount > 0) {
_delCount -= 1;
_items[_delCount] = item;
delCount.set(this, _delCount)
}
else{
for (let i = _count; i > 0; i--) {
_items[i] = _items[i-1]
}
_items[0] = item;
items.set(this, _items)
count.set(this, _count+1)
}
return this.size()
}
addAfter(item) {
let _items = items.get(this)
let _count = count.get(this);
_items[_count] = item;;
items.set(this, _items)
count.set(this, _count+1)
return this.size()
}
我们先说addAfter吧,很简单,我们就在count数量上加一,并将传入的元素,赋值到其位置上,就完了操作。而addBefore方法,会出现三种状况,第一种,就是本身队列是空的,那么我们直接让他走addAfter方法,比较如果count出现负数是不好看的。第二种则是之前删除过元素的情况(即delCount>0),相当于在之前删除的位置给其赋值,再delCount自然让其减一来完成。最后一种则为,该队列不是空的之前也没删除过值的记录(即delCount=0),我们会用for循环往下一层推当前层的值,最后给第0项赋值,并保存来完成的。
接下来,我们使用一下它:
import Deque from "./js/Deque"
const deque= new Deque();
console.log('addBefore->',deque.addBefore(1));
console.log('get->',deque.get());
console.log('addAfter->',deque.addAfter(2));
console.log('get->',deque.get());
console.log('addBefore->',deque.addBefore(0));
console.log('get->',deque.get());
console.log('addAfter->',deque.addAfter(3));
console.log('get->',deque.get());
我们分别在队列的前后各增加了两条数据,总共是4条,可以看到下面的结果是成功的。
3.peekBefore&peekAfter
peekBefore() {
if(this.size()==0) return undefined
return items.get(this)[delCount.get(this)]
}
peekAfter() {
if(this.size()==0) return undefined
return items.get(this)[count.get(this)-1]
}
peek只读取首和尾的值,很简单,peekBefore就读delCount的值,如果你未删除那么delCount就是0了,而peekAfter是读从count下一个值然后减去一即为最后一项了,这样就可以取出当前首和尾了。
接下来,我们使用一下它:
console.log('get->',deque.get());
console.log('peekBefore->',deque.peekBefore())
console.log('peekAfter->',deque.peekAfter())
这里我们看到队列的数据是值分别为0,1,2,3四个值,所以我们会分别取出0和3的,在下面可以看到是符合要求的。
4.removeBefore&removeAfter
removeBefore() {
if(this.size()==0) return undefined
let _items = items.get(this);
let _delCount = delCount.get(this);
let item = _items[_delCount];
delete _items[_delCount]
items.set(this,_items);
delCount.set(this,_delCount+1);
return item;
}
removeAfter() {
if(this.size()==0) return undefined
let _items = items.get(this);
let _count = count.get(this);
_count-=1;
let item = _items[_count];
delete _items[_count]
items.set(this,_items);
count.set(this,_count)
return item
}
与peek类似,只是remove会移除掉当前值,并且分别在会在delCount与count基础上做出对应变化,来给其计数。当然别忘了还要更新下刚才处理的元素。
接下来,我们使用一下它:
console.log('get->',deque.get());
console.log('removeBefore->',deque.removeBefore())
console.log('get->',deque.get());
console.log('removeAfter->',deque.removeAfter())
console.log('get->',deque.get());
我们会移除首位两个元素的值即0和3,期望是最后所剩下只有1和2,长度是2,下面可以看到也是符合期望的。
结语
本次我们学习实现了队列的一个变种——双端队列。他就像你在排队,售票员在队伍头有一个,而且在队伍尾也有一个。在程序中,可以做那种文件撤销回退那种功能,当然在前端写js完全可以用数组代替,但是,本次着重还是以思想为前提,去完成案例,而非单纯的实现。