1、链表简介
链表(Linked List)是一种常见的线性数据结构,它由一系列节点组成,每个节点包含两部分:数据和指向下一个节点的指针。
链表相对于数组而言,具有插入和删除操作高效、内存利用率高等优点。但由于链表中的元素不是连续存储的,因此在访问元素时需要从头节点开始遍历,导致访问效率较低。
本文将介绍如何在 JavaScript 中实现单向链表和双向链表。
2、单向链表
2.1 单向链表的定义
单向链表是最简单的链表类型。每个节点包含数据和一个指向下一个节点的指针。链表的最后一个节点指向 null。这种链表的特点是只能从头部向尾部遍历。
2.2 单向链表的节点类
首先,我们定义一个节点类 Node,用于表示链表中的每个节点。
class Node {
constructor(data) {
this.data = data; // 节点数据
this.next = null; // 指向下一个节点的指针
}
}
2.3 单向链表类
接下来,我们定义一个链表类 SinglyLinkedList,用于管理节点。
class SinglyLinkedList {
constructor() {
this.head = null; // 链表头
this.size = 0; // 链表大小
}
// 在链表末尾添加节点
append(data) {
const newNode = new Node(data);
if (!this.head) {
this.head = newNode;
} else {
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = newNode;
}
this.size++;
}
// 在指定位置插入节点
insertAt(data, index) {
if (index < 0 || index > this.size) {
return; // 索引无效
}
const newNode = new Node(data);
if (index === 0) {
newNode.next = this.head;
this.head = newNode;
} else {
let current = this.head;
let previous;
let count = 0;
while (count < index) {
previous = current;
current = current.next;
count++;
}
newNode.next = current;
previous.next = newNode;
}
this.size++;
}
// 删除指定位置的节点
removeAt(index) {
if (index < 0 || index >= this.size) {
return null; // 索引无效
}
let current = this.head;
if (index === 0) {
this.head = current.next;
} else {
let previous;
let count = 0;
while (count < index) {
previous = current;
current = current.next;
count++;
}
previous.next = current.next;
}
this.size--;
return current.data;
}
// 打印链表
print() {
let current = this.head;
let result = '';
while (current) {
result += current.data + ' -> ';
current = current.next;
}
console.log(result + 'null');
}
}
2.4 单向链表的使用示例
const list = new SinglyLinkedList();
list.append(10);
list.append(20);
list.append(30);
list.print(); // 10 -> 20 -> 30 -> null
list.insertAt(15, 1);
list.print(); // 10 -> 15 -> 20 -> 30 -> null
list.removeAt(2);
list.print(); // 10 -> 15 -> 30 -> null
3、双向链表
3.1 双向链表的定义
双向链表是每个节点都有两个指针,一个指向下一个节点,一个指向前一个节点。这种链表的特点是可以从头部向尾部遍历,也可以从尾部向头部遍历。
3.2 双向链表的节点类
我们定义一个双向节点类 DNode。
class DNode {
constructor(data) {
this.data = data; // 节点数据
this.next = null; // 指向下一个节点的指针
this.prev = null; // 指向前一个节点的指针
}
}
3.3 双向链表类
接下来,我们定义一个双向链表类 DoublyLinkedList。
class DoublyLinkedList {
constructor() {
this.head = null; // 链表头
this.tail = null; // 链表尾
this.size = 0; // 链表大小
}
// 在链表末尾添加节点
append(data) {
const newNode = new DNode(data);
if (!this.head) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
}
this.size++;
}
// 在指定位置插入节点
insertAt(data, index) {
if (index < 0 || index > this.size) {
return; // 索引无效
}
const newNode = new DNode(data);
if (index === 0) {
if (!this.head) {
this.head = newNode;
this.tail = newNode;
} else {
newNode.next = this.head;
this.head.prev = newNode;
this.head = newNode;
}
} else if (index === this.size) {
this.append(data);
return;
} else {
let current = this.head;
let count = 0;
while (count < index) {
current = current.next;
count++;
}
newNode.next = current;
newNode.prev = current.prev;
current.prev.next = newNode;
current.prev = newNode;
}
this.size++;
}
// 删除指定位置的节点
removeAt(index) {
if (index < 0 || index >= this.size) {
return null; // 索引无效
}
let current = this.head;
if (index === 0) {
this.head = current.next;
if (this.head) {
this.head.prev = null;
} else {
this.tail = null;
}
} else {
let count = 0;
while (count < index) {
current = current.next;
count++;
}
if (current.next) {
current.next.prev = current.prev;
} else {
this.tail = current.prev;
}
if (current.prev) {
current.prev.next = current.next;
}
}
this.size--;
return current.data;
}
// 打印链表
headPrint() {
let current = this.head;
let result = '';
while (current) {
result += current.data + ' <-> ';
current = current.next;
}
console.log(result + 'null');
}
// 打印链表
tailPrint() {
let current = this.tail;
let result = '';
while (current) {
result += current.data + ' <-> ';
current = current.prev;
}
console.log(result + 'null');
}
}
3.4 双向链表的使用示例
const dList = new DoublyLinkedList();
dList.append(10);
dList.append(20);
dList.append(30);
dList.headPrint(); // 10 <-> 20 <-> 30 <-> null
dList.tailPrint(); // 30 <-> 20 <-> 10 <-> null
dList.insertAt(15, 1);
dList.headPrint(); // 10 <-> 15 <-> 20 <-> 30 <-> null
dList.tailPrint(); // 30 <-> 20 <-> 15 <-> 10 <-> null
dList.removeAt(2);
dList.headPrint(); // 10 <-> 15 <-> 30 <-> null
dList.tailPrint(); // 30 <-> 15 <-> 10 <-> null
4、总结
- 单向链表:适用于需要频繁插入和删除操作的场景,时间复杂度为O(1)。
- 双向链表:既可以从头遍历到尾,又可以从尾遍历到头,适用于需要双向访问的场景。
希望本文能帮助你理解链表的基本概念和实现方法。通过实践,你可以进一步掌握链表的使用和优化。