链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列节点(链表中每一个元素称为节点)组成,节点可以在运行时动态生成。每个节点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个节点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
数组的长度是预先设定好的,想要额外添加元素或者删除元素是一件比较困难的事。那么使用链表的话恰恰就解决了这些问题,对于链表来说删除或添加一个元素是非常方便的,除了数据的随机访问(可以实现但是比较麻烦,比如可以通过添加和操作索引值来实现)
一般的链表都会额外添加一个头节点(作为辅助)和尾节点,例如:
数组元素靠它们的位置进行引用,链表元素则是靠相互之间的关系进行引用。在上图中,我们说男人跟在人类后面,而不说男人是链表中的第二个元素。遍历链表,就是跟着链接,从链表的首元素一直走到尾元素(但这不包含链表的头节点,头节点常常用来作为链表的接入点)。上图中另外一个值得注意的地方是,链表的尾元素指向一个null节点。
向单链表中插入一个节点,只需要修改它前面的节点(前驱),使其指向新加入的节点,而新加入的节点则指向原来前驱指向的节点。上图演示了如何在帅哥后加入吕小布。
从链表中删除一个元素也很简单,只需要将待删除元素的前驱节点指向待删除元素的后继节点。上图展示了从单链表中删除男神。
用JS设计一个单链表
我们需要设计两个类,Node 类用来表示节点,LinkedList类提供插入节点、删除节点、显示列表元素的方法,以及其他一些辅助方法。
Node类:
// 存储节点
function Node(element){
this.element = element
this.next = null
}
LinkedList类:
function LinkedList(item){
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)
newNode.next = currentNode.next
currentNode.next = newNode // 这句很重要,是整个链表连接起来的关键
},
// 查找某一节点的前节点(前驱)
findPrevious: function(item){
var currentNode = this.head
while (!(currentNode.next === null) && currentNode.next.element !== item) {
currentNode = currentNode.next
}
return currentNode
},
// 删除某一节点
remove: function(item){
var previousNode = this.findPrevious(item)
if (previousNode.next !== null) {
previousNode.next = previousNode.next.next
}
},
// 修改某一节点的数据
edit: function(item, newItem){
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
}
}
}
测试:
const classify = new LinkedList()
classify.insert('human', 'head') // 往头节点后面插入human节点
classify.insert('man', 'human') // 往human节点后面插入man节点
classify.insert('shuaige', 'man') // 往man节点后面插入shuaige节点
classify.insert('nanshen', 'shuaige') // 往shuaige节点后面插入nanshen节点
classify.display()
/*human
man
shuaige
nanshen*/
console.log(classify.find('human')) // {element: 'human', next: Node(下一个节点)}
console.log(classify.findPrevious('nanshen')) // {element: 'shuaige', next: Node(下一个节点)}
classify.remove('man') // 删除man节点
classify.display()
/*human
shuaige
nanshen*/
classify.edit('nanshen', 'lvxiaobu') // 把nanshen节点改成lvxiaobu
classify.display()
/*human
shuaige
lvxiaobu*/