前面介绍了列表对数据排序,当时底层存储数据的数据结构是数组。这里将讨论另外一种列表:链表,它的底层存储数据的数据结构是对象。
产生链表的背景
数组不总是组织数据的最佳数据结构。因为
- 在很多编程语言中,数组的长度是固定的,所以当数组已经填满时,再加入新的元素就会很困难,在数组中,添加和删除元素也能麻烦,需要将数组中的其它元素向前或向后平移。但是javascript的数组不存在这样的问题。
- javascript中的数组被实现成了对象,与其它语言(c++、Java)相比,效率很低。
如果数组在实际使用时很慢,就可以考虑使用链表来替代它。除了对数据的随机访问,链表可以用在任何可以使用一维数组的情况下。当然,如果需要随机访问,数组仍然是最优的选择。
定义链表
数组元素靠他们的位置进行引用,链表元素则是靠相互之间的关系进行引用。遍历链表,就是跟着链接,从链表的首元素一直走到尾元素(不包括头节点)链表是由一组节点组成的集合。每个节点都可以使用一个对象的引用指向它的后继。指向另一个节点的引用叫做:链。
在链表中插入一个节点的效率很高,向链表中插入一个节点,需要修改它前面的节点,使其指向新加入的节点,而新加入的节点则指向原来前驱指向的节点。
在链表中删除一个节点也很快,将待删除的元素的前驱节点指向待删除元素的后继节点,同时将待删除元素指向null,元素就删除成功了。
代码实现一个基于对象的链表
// Node类用来表示节点
function Node(element) {
this.element = element // element用来保存节点上的数据
this.next = null // next用来保存指向下一个节点的链接
}
// LinkedList类提供对链表进行操作的方法
function LinkedList() {
this.head = new Node('head')
}
LinkedList.prototype = {
constructor: LinkedList,
// 查找节点(遍历链表,查找给定数据)
find: function (item) {
var currNode = this.head
while (currNode.element !== item) {
currNode = currNode.next
}
return currNode
},
// 插入节点
insert: function (newElement, item) {
var newNode = new Node(newElement)
var current = this.find(item) // 找到插入节点的位置
newNode.next = current.next // 将新节点的next属性设置未‘后’节点的next属性对应的值
current.next = newNode // 设置‘后’节点的next属性指向新节点
},
// 查找要删除节点的前一个节点
findPrev: function (item) {
var currNode = this.head
while (!(currNode.next === null) && (currNode.next.element !== item)) {
currNode = currNode.next
}
return currNode
},
// 删除节点
remove: function (item) {
var prevNode = this.findPrev(item)
if (!(prevNode.next === null)) {
prevNode.next = prevNode.next.next // 让前一个节点指向了待删除节点的后一个节点
}
},
// 显示链表中的元素
dispaly: function () {
var currNode = this.head
while (!(currNode.next === null)) {
console.log(currNode.next.element)
currNode = currNode.next
}
}
}
// 使用
var people = new LinkedList()
people.insert('wu', 'head')
people.insert('qin', 'wu')
people.insert('hao', 'qin')
people.insert('is', 'hao')
people.insert('a', 'is')
people.insert('good', 'a')
people.insert('boy', 'good')
people.dispaly()
console.log('-------------------------------------')
people.remove('good')
people.dispaly()
复制代码
双向列表
如果算法中需要频繁地找某结点的前趋结点,单链表的解决方式是遍历整个链表,增加算法的时间复杂度,影响整体效率。
为了快速便捷地解决这类问题,在单向链表的基础上,给各个结点额外配备一个指针变量,用于指向每个结点的直接前趋元素。这样的链表被称为“双向链表”或者“双链表”。
双向链表中的结点有两个指针域,一个指向直接前趋,一个指向直接后继。(链表中第一个结点的前趋结点为NULL,最后一个结点的后继结点为NULL)
// Node类用来表示节点
function Node(element) {
this.element = element // element用来保存节点上的数据
this.next = null // next用来保存指向下一个节点的链接
this.previous = null // previous用来保存指向上一个节点的链接
}
// LinkedListBoth类提供对双向链表进行操作的方法
function LinkedListBoth() {
this.head = new Node('head')
}
LinkedListBoth.prototype = {
constructor: LinkedListBoth,
// 查找节点(遍历链表,查找给定数据)
find: function (item) {
var currNode = this.head
while (currNode.element !== item) {
currNode = currNode.next
}
return currNode
},
// 插入节点
insert: function (newElement, item) {
var newNode = new Node(newElement)
var current = this.find(item) // 找到插入节点的位置
newNode.next = current.next // 将新节点的next属性设置未‘后’节点的next属性对应的值
newNode.previous = current
current.next = newNode // 设置‘后’节点的next属性指向新节点
},
// 查找要删除节点的前一个节点
//findPrev: function (item) {
// var currNode = this.head
// while (!(currNode.next === null) && (currNode.next.element !== item)) {
// currNode = currNode.next
// }
// return currNode
//},
// 删除节点
remove: function (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
}
},
// 显示链表中的元素
dispaly: function () {
var currNode = this.head
while (!(currNode.next === null)) {
console.log(currNode.next.element)
currNode = currNode.next
}
},
// 查找最后节点
findLast: function () {
var currNode = this.head
while (!(currNode.next === null)) {
currNode = currNode.next
}
return currNode
},
// 反序显示双向链表中的元素
dispReverse: function () {
var currNode = this.head
currNode = this.findLast()
while (!(currNode.previous === null)) {
console.log(currNode.element)
currNode = currNode.previous
}
}
}
// 使用
var people = new LinkedListBoth()
people.insert('wu', 'head')
people.insert('qin', 'wu')
people.insert('hao', 'qin')
people.insert('is', 'hao')
people.insert('a', 'is')
people.insert('good', 'a')
people.insert('boy', 'good')
people.dispaly()
console.log('-------------------------------------')
people.remove('good')
people.dispaly()
console.log('-------------------------------------')
people.dispReverse()
复制代码
循环链表
如果你希望可以从后向前遍历列表,但是又不想付出额外代表来创建一个双向列表,那么就需要使用循环链表。从循环链表的尾节点向后移动,就等于从后向前遍历链表。