上篇数据结构那些事(一)已经介绍了数组,列表和栈。这篇我们将介绍队列,链表,字典。
队列也是一种列表,遵循先进先出的规则。
队列的两种主要操作是:向队列中插入新元素和删除队列中的元素。入队操作在队尾插入新元素,出队在队头删除元素。 接下来,我们来设计一个队列Queue类:
function Queue(){
this.dataStore = [];//存储
this.enqueue = enqueue;//入队
this.dequeue = dequeue;//出队
this.front = front;//队首
this.back = back;//队尾
this.toString = toString;//显示队列元素
this.empty = empty;//清除
}
实现上面的操作方法:
function enqueue(e){
this.dataStore.push(e);
}
function dequeue(e){
this.dataStore.shift(e)
}
function front(){
return this.dataStore[0]
}
function back(){
return this.dataStore[this.dataStore.length - 1]
}
function toString(){
let retStr = "";
for (let i = 0; i<this.dataStore.length; ++i){
retStr += this.dataStore[i] + "\n";
}
return retStr;
}
function empty(){
if(this.dataStore.length === 0){
return true;
}else{
return false;
}
}
上面我们实现了一个基础的队列。但现实生活中,可能会出现类似军人优先,重病患优先这样的场景,那样我们上面的队列就无法满足了。这时,我们需要一个优先队列。 从优先队列中删除元素时,需要考虑优先权的限制。我们来定义一下存储在队列中的元素:
function Person(name, code){
this.name = name;
this.code = code; //表示优先级,越小优先级越高
}
现在,需要重新定义dequeue()方法。
function dequeue(){
let p = this.dataStore[0].code;
for(let i =1 ; i < this.dataStore.length; ++i){
if(this.dataStore[i].code < p){
p = i
}
}
return this.dataStore.splice(p, 1);
}
链表:有时候我们使用数组效率比较低的时候就可以考虑使用链表。当然,如果你的场景需要随机访问,数组仍然比链表更合适。
链表元素依靠相互之间的关系进行引用。遍历链表,就是跟着链表,从链表的首元素一直走到尾元素,链表的尾元素一般指向null。

下图演示了在eggs后插入cookies操作


明白了链表的好处,接下来我们来设计实现一个链表:
我们设计的链表包含两个类。Node类用来表示节点,LinkedList类提供插入,删除,显示元素等方法。
function Node(e){
this.element = e;
this.next = null;
}
function LList(){
this.head = new Node("head");
this.find = find; //遍历链表,查找给定数据
this.findPrev = findPrev; //遍历链表,查找给定数据前面一个节点
this.insert = insert; //插入一个节点
this.remove = remove; //删除
this.display = display; //显示
}
实现上面的方法:
function find(e){
let currNode = this.head;
while(currNode.element !== e){
currNode = currNode.next;
}
return currNode;
}
function insert(newEl, e){
let newNode = new Node(newEl);
let current = this.find(e);
newNode.next = current.next;
current.next = newNode;
}
function display(){
let currNode = this.head;
while(currNode.next !== null){
console.log(currNode.next.element);
corrNode = currNode.next;
}
}
function findPrev(e){
let currNode = this.head;
while(currNode.next !== null && currNode.next.element !== e){
currNode = currNode.next;
}
return currNode;
}
function remove(e){
let prev = this.findPrev(e);
if(prev.next !== null){
prev.next = prev.next.next;
}
}
上面定义的基础链表存在一个问题:很难从后向前遍历
这时,我们可以考虑实现一个双向链表:

function Node(element) {
this.element = element;
this.next = null;
this.previous = null;
}
我们还要修改对应的insert方法,因为他现在需要建立两个连接
function insert(newElement, item) {
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
newNode.previous = current;
current.next = newNode;
}
双向链表的remove()方法比单向链表的效率更高,因为不需要查找前驱节点了。
function remove(item) {
var currNode = this.find(item);
if (!(currNode.next == null)) {
currNode.previous.next = currNode.next;
currNode.next.previous = currNode.previous;
currNode.next = null;
currNode.previous = null;
}
}
还有一种链表是循环链表:和单向链表类似,唯一的区别是,在创建循环列表时,让其头结点的next属性指向它本身。

function LList() {
this.head = new Node("head");
this.head.next = this.head; //让头结点的next指向它自己
this.find = find;
this.insert = insert;
this.display = display;
this.findPrevious = findPrevious;
this.remove = remove;
}
字典:字典是一种以键值对形式存储数据的数据结构。
JavaScript中的Object类就是以字典形式设计的。所以字典的实现就像对象一样,感觉实在没什么值得写的,这里给一个demo。
function Dictionary(){
this.dataStore = new Array();
}
function add(key, value){
this.dataStore[key] = value;
}
function find(key){
return this.dataStore[key];
}
function remove(key){
delete this.dataStore[key]
}
function show(){
for( let key in Object.keys(this.dataStore)){
console.log(key + this.dataStore[key])
}
}