一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第24天,点击查看活动详情。
前言
自上次写完链表实现已过去一周左右。今天我们一起来实现链表的进阶之双向链表。什么是双向链表?
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。——百度百科
简单讲,我们上次实现的链表中有一个next指针指向下一个节点,双向链表就是每个节点不止有next指针指向后继节点,还有prev指针指向前驱节点。由这种节点组成的链表就是双向链表。
说了这么多,我们来编码实现一下。
实现
创建一个双向指针的节点类
class DoubNode extends Node {
// 继承自链表的节点Node
constructor(data, next, prev){
super(data, next)
this.prev = prev
}
}
创建一个双向链表类
class DoubList extends LinkedList {
constructor(){
super()
// 保存链表最后一个元素的引用
this.tail = null
}
}
既然是继承自链表类,那么我们就只需要重写不同于链表的方法就行。
首先来看看任意位置添加元素
pushAnyWhere() and push()
pushAnyWhere(data, index){
if(index >= 0 && index <= this.count) {
const node = new DoubNode(data)
let current = this.head
// 第一种情况,头部添加
if(index === 0) {
if(this.head === null) {
this.head = node
this.tail = node
}else {
node.next = this.head
current.prev = node
this.head = node
}
}else if(index === this. count) {
// 第二种情况,尾部添加
current = this.tail
current.next = node
node.prev = current
this.tail = node
}else{
// 第三种情况,中间
// 获取要插入位置的前驱节点
const prevNode = this.getData(index - 1)
current = prevNode.next
node.next = current
prevNode.next = node
current.prev = node
node.prev = prevNode
}
this.count++
}else{
console.log(`index输入不合法,请输入0-${this.count}之间的值`)
}
}
push(data) {
const node = new DoubNode(data);
if (this.head == null) {
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
node.prev = this.tail;
this.tail = node;
}
this.count++;
}
有添加就又删除,现在来实现任意位置删除
removeAnyWhere()
removeAnyWhere(index) {
if(index >= 0 && index < this.count) {
let current = this.head
// 删除头节点
if(index === 0) {
this.hed = current.next
// 特殊情况处理,如果只有一个头节点
if(this.count === 1) {
this.tail = undefined
}else {
this.head.prev = undefined
}
}else if(index === this.count - 1){
// 尾节点
current = this.tail
this.tail = current.prev
this.tail.next = undefined
}else {
current = this.getData(index)
const prevNode = current.prev
prevNode.next = current.next
current.next.prev = prevNode
}
this.count--
}else{
console.log(`index输入不合法,请输入0-${this.count}之间的值`)
}
}
总结
通过实现双向链表,我们发现,由于双向链表比普通链表多了prev指针,所以我们需要一只tail指针指向尾节点,帮助我们简化对其尾部的操作。同时,我们也需要对普通链表的插入与删除做一些调整,使prev指针指向其对应的前驱节点。getData(),indexOf()等其他方法都是公用的,所以我们创建的类要继承普通类。简化我们的代码量。下次我们用同样的方法来实现循环链表。