小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
链表(单向链表和双向链表)
上一节我们使用数组实现了堆栈和队列,链表和数组一样,也实现了按照一定的顺序存储元素,不同的地方在于链表不能像数组一样通过下标访问,而是每一个元素都能够通过“指针”指向下一个元素。我们可以直观地得出结论:链表不需要一段连续的存储空间,“指向下一个元素”的方式能够更大限度地利用内存。
根据上述内容,我们可以总结出链表的优点在于:
- 链表的插入和删除操作的时间复杂度是常数级的,我们只需要改变相关节点的指针指向即可;
- 链表可以像数组一样顺序访问,查找元素的时间复杂度是线性的。
要想实现链表,我们需要先对链表进行分类,常见的有单链表和双向链表。
| 链表类别 | 特点 |
|---|---|
| 单链表:单链表是维护一系列节点的数据结构 | 每个节点包含了数据,同时包含指向链表中下一个节点的指针 |
| 双向链表:不同于单链表 | 每个节点分支除了包含其数据以外,还包含了分别指向其前驱和后继节点的指针 |
首先,根据双向链表的特点,我们实现一个节点构造函数(节点类),如下代码:
class Node {
constructor(data) {
// data 为当前节点存储的数据
this.data = data
// next 指向下一个节点
this.next = null
// prev 指向上一个节点
this.prev = null
}
}
来初步实现双向链表类:
class DoubleLinkedList() {
constructor() {
// 双向链表开头
this.head = null
// 双向链接结尾
this.tail = null
}
}
实现双向链表原型上的一些方法:
add 链表尾部增加一个节点
add(item) {
let node = new Node(item)
// 如果当前链表还没有头
if (!this.head) {
this.head = item
this.tail = item
} else { // 如果当前链表已经有头
// 当前的尾作为新节点的prev
node.prev = this.tail
// 当前尾的next为新节点
this.tail.next = node
// 新的尾为新节点
this.tail = node
}
}
addAt 指定位置增加一个节点
addAt(index, item) {
let current = this.head
// 维护查找时当前节点的索引
let counter = 1
let node = new Node(item)
// 如果在头部插入
if (index === 0) {
this.head.prev = node
node.next = this.head
this.head = node
}
// 非头部插入,需要从头开始,找寻插入位置
else {
while(current) {
current = current.next
if( counter === index) {
node.prev = current.prev
current.prev.next = node
node.next = current
current.prev = node
}
counter++
}
}
}
remove 删除链表指定数据项
remove(item) {
let current = this.head
while (current) {
// 找到了目标节点
if (current.data === item ) {
// 目标链表只有当前目标项,即目标节点即是链表头又是链表尾
if (current == this.head && current == this.tail) {
this.head = null
this.tail = null
}
// 目标节点为链表头
else if (current == this.head ) {
this.head = this.head.next
this.head.prev = null
}
// 目标节点为链表尾部
else if (current == this.tail ) {
this.tail = this.tail.prev;
this.tail.next = null;
}
// 目标节点在链表首尾之间,中部
else {
current.prev.next = current.next;
current.next.prev = current.prev;
}
}
current = current.next
}
}
reverse 翻转链表
reverse() {
let current = this.head
let prev = null
while (current) {
let next = current.next
// 前后倒置
current.next = prev
current.prev = next
prev = current
current = next
}
this.tail = this.head
this.head = prev
}
traverse 遍历链表
traverse(fn) {
let current = this.head
while(current !== null) {
// 执行遍历时回调
fn(current)
current = current.next
}
return true
}
find 查找某个节点的索引
find(item) {
let current = this.head
let counter = 0
while( current ) {
if( current.data == item ) {
return counter
}
current = current.next
counter++
}
return false
}
最后说一句
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。