要存储多个元素,数组可能是最常用的数据结构。这种数据结构非常方便,提供了便利的语法来访问它的元素。然而,这种数据结构有一个缺点:(在大多数语言中)数组的大小是固定的,从数组的起点或中间插入或移除项的成本很高,因为需要移动元素。虽然 JavaScript 的 Array 类方法可以帮我们做这些事,但背后的情况同样是这样。
链表存储有序的元素集合,但不同于数组,链表的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也成为指针或链接)组成。
下图展示了一个链表的结构:

相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其它元素。然后,链表需要使用指针,因此实现链表时需要额外注意。
数组的另一个细节是可以直接访问任何位置的任何元素,想要访问链表中间的一个元素,需要从起点(表头)开始迭代列表直到找到所需的元素。
先规划一下链表将实现什么方法:
- append:向列表尾部添加元素
- insert:向列表任意位置添加元素
- removeAt:从列表特定位置移除元素
- remove:从列表中删除第一个与给定元素相匹配的元素
- indexOf:返回给定元素在列表中的索引
- isEmpty:查看列表是否为空
- size:查看列表中的元素个数
- getHead:获取列表中第一个元素
- toString:以字符串的形式输出列表中的所有元素
class Node {constructor(element) {// 要添加到列表中的值this.element = element// 指向列表中下一个节点项的指针this.next = null}}class LinkedList {constructor() {this.length = 0 // 列表元素的数量this.head = null // 第一个节点的引用}/*** 向列表尾部添加元素* @param {any} element*/append(element) {let node = new Node(element) // 创建一个 Nodeif (this.head === null) {// 如果列表为空,则将添加的元素设置为列表的第一个元素this.head = node} else {// 向不为空的列表添加元素// 要向列表的尾部添加一个元素,首先需要找到最后一个元素let current = this.head // 用于存储列表的最后一个元素while (current.next) {// 循环列表,直到找到最后一项// 最后一个元素的 next 为 null,因此找到最后一个元素之后将不会再进入循环current = current.next}// 找到最后一项,将其 next 赋为 node,建立链接current.next = node}// 列表的长度+1this.length++}/*** 向列表的特定位置插入一个新元素* @param {number} position* @param {any} element*/insert(position, element) {// 插入的有效位置为 0 至 this.lengthif (position < 0 || position > this.length) {return false // 表示插入失败}let node = new Node(element)if (position === 0) {// 在第一个位置添加元素node.next = this.headthis.head = node} else {let previous = null // 用于存储位置为 position 前一位的元素let current = this.head // 用于存储位置为 position 的元素for (let i = 1; i <= position; i++) {previous = currentcurrent = current.next}previous.next = nodenode.next = current}this.length++return true}/*** 从列表的特定位置移除一项* @param {number} position*/removeAt(position) {// 检查越界值,当 position 在 0 到 this.length - 1 才是有效位置if (position < 0 || position >= this.length) {return null // 表示没有移除元素}let removedElement // 用于存储被移除的元素if (position === 0) {removedElement = this.head // 记录被移除的元素// 如果删除的是列表的第一个元素,只需要将 this.head 指向第二个元素即可this.head = this.head.next} else {let current = this.head // 用于存储 position 所指的元素let previous = null // 用于存储 position 前一位元素// 找出 position 所指的元素,以及 position 的前一个元素// 当跳出循环之后,current 为 position 所对应的元素for (let i = 1; i <= position; i++) {previous = currentcurrent = current.next}removedElement = current // 记录被移除的元素// 将 prevous 与 current 的下一项链接起来:跳过 current,从而移除它previous.next = current.next}this.length--return removedElement}/*** 从列表中删除第一个与给定元素相匹配的元素* @param {any} element*/remove(element) {let result = falseif (this.head.element === element) {// 移除的项为第一个元素this.head = this.head.nextresult = true // 表示删除元素成功} else {// 移除的项并非第一个元素let previous = this.headlet current = this.head.nextwhile (current) {if (current.element === element) {previous.next = current.nextresult = true // 表示删除元素成功}previous = currentcurrent = current.next}}return result}/*** 返回元素在列表的索引* @param {any} element*/indexOf(element) {let current = this.headlet index = 0while (current) {if (element === current.element) {return index}index++current = current.next}return -1}/*** 如果链表中不包含任何元素,返回 true,否则返回 false*/isEmpty() {return this.length === 0}/*** 返回链表包含的元素个数*/size() {return this.length}/*** 获取第一个元素*/getHead() {return this.head}/*** 由于列表项使用了 Node 类,需要重写继承自 JavaScript 对象默认的 toString 方法,* 让其只输出元素的值。*/toString() {let current = this.headlet string = ''while (current) {string += current.element + (current.next ? ',' : '')current = current.next}return string}}
