链表的特点是长度不固定,不用担心插入新元素的时候新增位置的问题。插入一个元素的时候,只要找到插入点就可以了,不需要整体移动整个结构。尽管从链表中头节点遍历到尾节点很容易,但是反过来,从后向前遍历就没有那么简单。我们可以通过给Node对象增加一个属性,该属性存储指向前驱节点的链接,这样就容易多了。此时向链表中插入一个节点需要更多的工作,我们需要指出该节点正确的前驱和后续。但是在从链表中删除节点的时候效率更高了,不需要再查找待删除节点的前驱节点了。双链表的图解如下:
用JS设计一个双链表
我们需要设计两个类,Node 类用来表示节点,LinkedList类提供插入节点、删除节点、显示列表元素的方法,以及其他一些辅助方法。
Node类:
function Node(element){
this.element = element
this.next = null
this.previous = null
}
LinkedList类:
function LinkedList(){
this.head = new Node('head') // 头节点
}
用LinkedList的原型对象存储操作链表的方法:
LinkedList.prototype = {
// 查找某一节点
find: function(item){
var currentNode = this.head
while (currentNode.element !== item) {
currentNode = currentNode.next
}
return currentNode
},
// 往某一节点后面插入新节点
insert: function(newItem, item){
var newNode = new Node(newItem)
var currentNode = this.find(item)
if (currentNode.next !== null) {
newNode.next = currentNode.next
newNode.next.previous = newNode
newNode.previous = currentNode
currentNode.next = newNode
} else {
currentNode.next = newNode
newNode.previous = currentNode
newNode.next = null
}
},
// 删除一个节点
remove: function(item){
var currentNode = this.find(item)
if (currentNode.previous !== null && currentNode.next !== null) { // 首先不是头尾节点的情况
currentNode.previous.next = currentNode.next
currentNode.next.previous = currentNode.previous
currentNode.previous = null // 如果不写,链表就会多出一条小分支
currentNode.next = null // 如果不写,链表就会多出一条小分支
} else if (currentNode.previous === null) { // 当是头节点的时候
currentNode.next.previous = null
currentNode.next = null
} else if (currentNode.next === null) { // 当是尾节点的时候
currentNode.previous.next = null
currentNode.previous = null
}
},
// 找到最后一个节点
findLast: function(){
var currentNode = this.head
while (currentNode.next !== null) {
currentNode = currentNode.next
}
return currentNode
},
// 将要添加的节点放在链表末尾
append: function(item){
var lastNode = this.findLast()
var newNode = new Node(item)
lastNode.next = newNode
newNode.previous = lastNode
newNode.next = null
},
// 修改节点信息
edit: function(item, newItem){
// var currentNode = new Node(item) // 这样会重新分配存储空间,改变不了链表的节点
var currentNode = this.find(item)
currentNode.element = newItem
},
// 打印链表所有节点
display: function(){
var currentNode = this.head
while (currentNode.next !== null) {
console.log(currentNode.next.element)
currentNode = currentNode.next
}
},
// 反向打印双链表的节点
displayReverse: function(){
var currentNode = this.findLast()
while (currentNode.previous !== null) {
console.log(currentNode.element) // 这样其实是不会打印head的
currentNode = currentNode.previous
}
},
}
测试:
const classify = new LinkedList()
classify.insert('human', 'head')
classify.insert('man', 'human')
classify.insert('shuaige', 'man')
classify.insert('nanshen', 'shuaige')
classify.display()
/*human
man
shuaige
nanshen*/
console.log(classify.find('man')) // {element: 'man', next: Node(下一个节点), previous: Node(上一个节点)}
classify.remove('nanshen') // 删除“nanshen”节点
console.log(classify.findLast()) // {element: 'shuaige', next: null, previous: Node(上一个节点)}
classify.append('lvxiaobu') // 往链表尾部插入“lvxiaobu”
classify.display()
/*human
man
shuaige
lvxiaobu*/
classify.edit('lvxiaobu', 'huge')
classify.displayReverse() // 反向打印链表
/*huge
shuaige
man
human*/