# 链表(Linked-list)
如果你发现数组在实际使用时很慢,就可以考虑使用链表来替代它。除了对数据的随机访问,链表几乎可以用在任何可以使用一维数组的情况中。如果需要随机访问,数组仍然是更好的选择。
# 链表的定义
另一个例子是寻宝游戏。你有一条线索,这条线索就是指向寻找下一条线索的地点的指针。你顺着这条链接去下一个地点,得到另一条指向再下一处的线索。得到链表中间的线索的唯一办法,就是从起点(第一条线索)顺着链表寻找。
还有一个可能是用来说明链表的最流行的例子,那就是火车。一列火车是由一系列车厢(也称车皮)组成的。每节车厢或车皮都相互连接。你很容易分离一节车皮,改变它的位置、添加或移除它。下图演示了一列火车。每节车皮都是链表的元素,车皮间的连接就是指针。
# Node类
Node类包含连个属性: element 用来保存节点上的数据,next 用来保存指向下一个节点的链接,具体实现如下:
function Node(element) {
this.element = element; // 当前节点的元素
this.next = null; // 下一个节点链接
}
# LinkedList类
LinkedList类提供了对链表进行操作的方法,包括插入删除节点,查找给定的值等。值得注意的是,它只有一个 属性,那就是使用一个 Node 对象来保存该链表的头节点。
function linkedList() {
let head = null, // 第一个链表 默认标识
length = 0, // 链表长度
current; // 当前传入元素
// 添加
this.push = (el) => {
const node = new Node(el)
if (head === null) { // 插入第一个链表
head = node;
} else {
current = head;
while (current.next) { // 获取最后一个链表
current = current.next
}
current.next = node // 将最后一个链表的next赋值为新元素
}
length++ // 链表长度
}
// 移除
this.removeAt = (index) => {
if (index >= 0 && index < length) { // 是否越界
// 移除第一个链表,特殊对待
if (index === 0) {
head = head.next
} else {
let previous;
current = head;
for (let i = 0; i < index; i++) {
previous = current
current = current.next
}
// 移除当前索引的 current
previous.next = current.next
}
length--
return head
}
return undefined;
}
// 指定位置添加
this.insert = (el, index) => {
if (index >= 0 && index < length) { // 是否越界
const node = new Node(el);
if (index === 0) {
current = head
node.next = current
head = node
} else {
let previous;
current = head;
for (let i = 0; i < index; i++) {
previous = current
current = current.next
}
// 介于 previous & current 两者间插入
previous.next = node
node.next = current
}
length++
return head
}
return false;
}
// 查找是否存在,有 => 索引,否 => -1
this.indexOf = (el) => {
current = head
for (let i = 0; i < length; i++) {
if (current.element === el) {
return i
}
current = current.next
}
return -1
}
// 移除
this.remove = (element) => {
this.removeAt(this.indexOf(element))
}
// 是否为空
this.isEmpty = () => {
return length === 0
}
// 获取长度
this.size = () => {
return length
}
// 获取最开头的链表
this.getHead = () => {
return head
}
// 打印链表
this.toString = () => {
if (head === null) {
return ''
}
current = head
let str = current.element
while (current.next) {
current = current.next
str += current.element
}
return str
}
}
# 双向链表
双向链表和普通链表的区别在于,在链表中,一个节点只有链向下一个节点的链接;
而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向上一个元素
function DoublyLinkedList() {
let head = null, // 第一个链表
tail = null, // 最后一个链表
length = 0, // 链表长度
current, previous; // 当前传入元素
// 指定位置添加
this.insert = (el, index) => {
if (index >= 0 && index < length + 1) { // 是否越界
const node = new DoublyNode(el);
if (index === 0) {
if (head == null) { // ① 链表内元素为空
head = node
tail = node
} else { // 链表内存在元素
current = head
current.prev = node
node.next = current
head = node
}
} else if (index === length) { // ② 在末尾插入元素
current = tail
current.next = node
node.prev = current
tail = node
} else { // ③ 在链表中插入元素
current = head
for (let i = 0; i < index; i++) {
previous = current
current = current.next
}
previous.next = node
node.next = current
current.prev = node
node.prev = previous
}
length++
console.log(head, tail)
return true
}
return false;
}
// 指定位置移除
this.removeAt = (index) => {
if (index >= 0 && index < length) { // 是否越界
if (index === 0) {
if (length === 1) {
head = null
tail = null
} else {
current = head
head = current.next
head.prev = current.prev
}
} else if (index === length - 1) {
current = tail
tail = current.prev
tail.next = current.next
} else {
current = head
for (let i = 0; i < index; i++) {
previous = current
current = current.next
}
previous.next = current.next
current.next.prev = previous
}
return true
}
return undefined
}
this.log = function() {
current = head
let str = current.element
while (current.next) {
current = current.next
str = str + ' ' + current.element
}
return str
}
}
# 循环链表
循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用。循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用undefined,而是指向第一个元素(head)