最近学习了链表的基本概念后,打算自己用JavaScript实现一个单向链表类及相关的方法。首先实现一个 Node 类,主要包括两个实例属性:data(该节点的数据)和 next(指向下一个节点)。单项链表成员可以分为:链表头部(最初指向null)、中间节点(next指向另一个Node)、链表尾部(next指向null)。
// 链表节点
function Node({ data, next = null }) {
this.data = data
this.next = next
}
function LinkedList() {
this.head = null
this.length = 0
// 获取某个位置的节点
LinkedList.prototype.get = function (position) {
// 空链表
let preNode = this.head
if (!preNode) return null
// 只有一个节点
let pos = 0
if (position === pos) {
return preNode
}
// 一个以上节点
while (preNode.next) {
pos++
if (position === pos) {
return preNode.next
}
preNode = preNode.next
}
// 超过链表长度
return null
}
// 同 get(position)
LinkedList.prototype.last = function () {
// 空链表
let preNode = this.head
if (!preNode) return null
// 非空链表
while (preNode.next) {
preNode = preNode.next
}
return preNode
}
// 获取某个节点所在位置
LinkedList.prototype.indexOf = function (node) {
// 空链表
let preNode = this.head
if (!preNode) return null
// 只有一个节点
let pos = 0
if (preNode === node) {
return pos
}
// 一个以上节点
while (preNode.next) {
pos++
if (preNode.next === node) {
return pos
}
preNode = preNode.next
}
// 超过链表长度
return null
}
// 向链表末尾添加节点
LinkedList.prototype.append = function (node) {
isNodeValid.call(this, node)
if (!this.head) {
this.head = node
return ++this.length
}
const lastNode = this.last()
lastNode.next = node
return ++this.length
}
// 某个位置插入节点
LinkedList.prototype.insert = function (position, node) {
isPosValid.call(this, position)
isNodeValid.call(this, node)
// 首节点
if (position === 0) {
node.next = this.head
this.head = node
this.length++
} else {
// 除了首节点
let preNode = this.get(position - 1)
let curNode = this.get(position)
node.next = curNode
preNode.next = node
this.length++
}
return this
}
// 移除某个位置的节点
LinkedList.prototype.removeAt = function (position) {
isPosValid.call(this, position)
const curNode = this.get(position)
// 首节点
if (position === 0) {
this.head = curNode.next
} else {
// 非首节点
const preNode = this.get(position - 1)
preNode.next = curNode.next
}
this.length--
return curNode
}
// 替换某个位置的节点
LinkedList.prototype.replace = function (position, node) {
isPosValid.call(this, position)
isNodeValid.call(this, node)
const curNode = this.get(position)
// 首节点
if (position === 0) {
node.next = curNode.next
this.head = node
} else {
// 非首节点
const preNode = this.get(position - 1)
node.next = curNode.next
preNode.next = node
}
return this
}
// 获取所有值
LinkedList.prototype.values = function () {
const arr = []
let preNode = this.head
while (preNode) {
arr.push(preNode.data)
preNode = preNode.next
}
return arr
}
// 判断节点位置是否有效
function isPosValid(position) {
if (position < 0 || !Number.isInteger(position)) {
throw new TypeError('position 应该为非负整数.')
}
// 无节点时
if (!this.head) {
throw new RangeError('链表为空.')
}
// 超出范围
if (position > this.length - 1) {
throw new RangeError('超出链表范围.')
}
}
// 判断节点是否有效
function isNodeValid(node) {
if (!(node instanceof Node)) {
console.warn(node)
throw new TypeError('传入节点为非 Node 节点.')
}
// 将链表中所有的节点放到 WeakSet 中, 便于判断添加节点的唯一性, 避免节点之间相互引用
const allNodes = new WeakSet()
let preNode = this.head
while (preNode) {
allNodes.add(preNode)
preNode = preNode.next
}
if (allNodes.has(node) || allNodes.has(node.next)) {
throw new Error('该对象已存在于链表中, 请勿重复添加.')
}
}
}
// 测试
l = new LinkedList()
o1 = new Node({ data: 1 })
o2 = new Node({ data: 2 })
o3 = new Node({ data: 3, next: new Node({ data: '指定 Node 的 next' }) })
o4 = new Node({ data: 4 })
l.append(o1)
l.append(o2)
l.append(o3)
l.append(o4)
o5 = new Node({ data: 5 })
l.insert(0, o5)
console.log('===> ', l)
console.log('===> ', l.get(3))
console.log('indexOf ===> ', l.indexOf(o2))
console.log('values ===> ', l.values())
至于双向链表,大概思路是给Node实例添加一个pre属性,作用是指向前一个节点。每次向指定位置添加新节点node的时候,就将 node.pre 指向 该位置的前一个节点preNode、node.next 指向 preNode.next、preNode.next.pre指向node,然后preNode.next指向新节点node;删除节点同理。