# 介绍
## 存储结构
链表(Linked List):是一种 `非连续`、`非顺序` 存储的线性结构。
链表由一个个的 `节点` 组成,每个节点在内存中 `位置是随机` 不确定的(并不是顺序的)。为了让节点之间有联系,在每个节点中都有一个指针指向下一个节点。
![](https://img.kancloud.cn/d3/e9/d3e97f945ee236abe9f0852d0ccd8739_1538x230.png)
## 特点
`高效` 的 插入、删除操作。(只需要修改指针的反向即可, 不需要移动数据)
`低效` 的 随机读取一个元素。(只为没有下标,所以我们必须通过链表头一个一个节点的向下查找想要的元素)
高效的插入:
添加新节点只需要:
1. 将上一个节点指向新节点
2. 将新节点指向下一个节点
图标:向两个红色节点中插入一个新蓝色节点:
![](https://img.kancloud.cn/34/5d/345d1a7ddd6bddf9b56da9e2cfb38de9_456x266.png)
## 类型
链表又可以分为:
- 单向链表
- 双向链表
- 循环链表
- 双向循环链表
单链表
![](https://img.kancloud.cn/08/a8/08a86e3a234cda7c380b3ea3a9321cd9_1522x134.png)
循环链表:
![](https://img.kancloud.cn/05/f6/05f6e4032428f4923cf2361384c18195_1454x190.png)
双向链表
![](https://img.kancloud.cn/3e/d2/3ed24ada31b98c8e497e88c555c92b94_1502x142.png)
双向循环链表
![](https://img.kancloud.cn/de/6a/de6ab3e50b682cf9e2d6391d086885ab_1348x192.png)
# 代码实现
在 JS 语言中没有提供链表这种数据结构(Java 中提供了),所以当我们需要使用这种数据结构时,我们需要自己实现一套链表功能。
比较适合使用 "面向对象" 的方式来实现。
JS 中的面向对象有两种写法:
## 写法一、传统的写法 (使用 function 定义类)
定义类时两大原则:
1. 使用 function 来定义类
2. 类中的方法定义在原型上(prototype)
如下图的写法和下面 ES6 中的写法功能完全相同:
![](https://img.kancloud.cn/e7/09/e709ec3702f94778ba6d3f065e97ca1f_482x1682.png)
## 写法二、ES6 中新语法 (使用 class 定义类)
~~~
// 定义一个节点类
class Node {
constructor(data) {
this.data = data
this.next = null
}
}
// 链表类(管理所有的节点)
class LinkedList {
constructor() {
this.length = 0 // 链表中节点的数量
this.head = null // 链表的头节点
this.tail = null // 指向最后一个节点
}
// 向链表的最后添加一个元素
append(data) {
// 1. 先创建一个新节点
let newNode = new Node(data)
// 2. 把这个新节点挂到链表的最后
// 如果当前链表是空,那么这个新节点即是头节点,又是尾节点
if(this.tail === null) {
this.head = newNode
this.tail = newNode
} else {
// 让尾节点的 next 指向新节点(挂到最后)
this.tail.next = newNode
// 让tail指向指向这个新的节点(这个新节点现在是尾节点了)
this.tail = this.tail.next
}
// 链表长度+1
this.length++
}
// 清除链表
clear() {
this.length = 0
this.head = null
this.tail = null
}
// 删除第 index 个节点
delete(index) {
// 1. 先找到第 index - 1 个节点(前一个节点)
let prev = this.getNodeByIndex(index-1)
// 2. 让它的前一个节点的 next 指向,当前这个节点的 next
prev.next = prev.next.next
// 3. 长度-1
this.length--
}
// 根据下标找一个节点
getNodeByIndex(index) {
// 要查找的下标是否在链表范围内
if(index > this.length - 1) return null
// 从头节点开始向后跳 index 次
let current = this.head // 头节点
for (let i = 0; i < index; i++) {
// 当前节点指向下一个节点
current = current.next
}
return current
}
// 把链表转成一个字符串,打印时看着好看
toString() {
let ret = ''
if(this.length === 0) return ''
// 取出第一个节点
let current = this.head
// 把节点数据拼到字符串上
ret += current.data + '->'
// 循环链表中所有的节点并输出
while (current.next !== null) {
// 当前节点指向下一个节点
current = current.next
// 取出节点上的数据拼到字符串上输出
ret += current.data + '->'
}
return ret + 'null'
}
}
~~~
测试代码:
~~~
// 创建一个链表
const lb = new LinkedList()
// 向链表中追回几条记录
lb.append('a')
lb.append('b')
lb.append('c')
lb.append('d')
lb.append('hello')
lb.append('boy')
// 打印链表结构
console.log(lb.toString())
lb.delete(4) // 删除第5个元素:hello
console.log(lb.toString())
~~~
运行结果:
~~~
删除前:
a->b->c->d->hello->boy->null
删除后:
a->b->c->d->boy->null
~~~