单链表
单链表
单链表是链表最基本的形式,每个节点包含数据和指向下一个节点的指针。它支持动态内存分配,并能高效地完成插入和删除操作。
节点结构
class ListNode {
constructor(data) {
this.data = data; // 数据域
this.next = null; // 指针域,指向下一个节点
}
}完整实现
class SinglyLinkedList {
constructor() {
this.head = null; // 头指针
this.size = 0; // 链表长度
}
// 头部插入节点
prepend(data) {
const newNode = new ListNode(data);
newNode.next = this.head;
this.head = newNode;
this.size++;
}
// 尾部插入节点
append(data) {
const newNode = new ListNode(data);
// 若链表为空,新节点成为头节点
if (!this.head) {
this.head = newNode;
this.size++;
return;
}
// 找到最后一个节点
let current = this.head;
while (current.next) {
current = current.next;
}
// 链接新节点
current.next = newNode;
this.size++;
}
// 在指定位置插入节点
insert(index, data) {
if (index < 0 || index > this.size) {
throw new Error("Index out of bounds");
}
// 头部插入
if (index === 0) {
this.prepend(data);
return;
}
const newNode = new ListNode(data);
let current = this.head;
// 找到插入位置的前一个节点
for (let i = 0; i < index - 1; i++) {
current = current.next;
}
// 插入新节点
newNode.next = current.next;
current.next = newNode;
this.size++;
}
// 删除头节点
removeFirst() {
if (!this.head) {
return null;
}
const removedData = this.head.data;
this.head = this.head.next;
this.size--;
return removedData;
}
// 删除尾节点
removeLast() {
if (!this.head) {
return null;
}
// 只有一个节点
if (!this.head.next) {
const removedData = this.head.data;
this.head = null;
this.size--;
return removedData;
}
// 找到倒数第二个节点
let current = this.head;
while (current.next.next) {
current = current.next;
}
const removedData = current.next.data;
current.next = null;
this.size--;
return removedData;
}
// 删除指定位置的节点
remove(index) {
if (index < 0 || index >= this.size) {
throw new Error("Index out of bounds");
}
// 删除头节点
if (index === 0) {
return this.removeFirst();
}
let current = this.head;
// 找到被删节点的前一个节点
for (let i = 0; i < index - 1; i++) {
current = current.next;
}
const removedData = current.next.data;
current.next = current.next.next;
this.size--;
return removedData;
}
// 删除指定值的第一个节点
removeByValue(data) {
if (!this.head) {
return false;
}
// 如果头节点就是要删除的节点
if (this.head.data === data) {
this.head = this.head.next;
this.size--;
return true;
}
let current = this.head;
while (current.next && current.next.data !== data) {
current = current.next;
}
// 找到了要删除的节点
if (current.next) {
current.next = current.next.next;
this.size--;
return true;
}
return false; // 未找到
}
// 查找元素
indexOf(data) {
let current = this.head;
let index = 0;
while (current) {
if (current.data === data) {
return index;
}
current = current.next;
index++;
}
return -1; // 未找到
}
// 获取指定位置的元素
get(index) {
if (index < 0 || index >= this.size) {
throw new Error("Index out of bounds");
}
let current = this.head;
for (let i = 0; i < index; i++) {
current = current.next;
}
return current.data;
}
// 检查链表是否包含指定元素
contains(data) {
return this.indexOf(data) !== -1;
}
// 获取链表长度
length() {
return this.size;
}
// 检查链表是否为空
isEmpty() {
return this.size === 0;
}
// 清空链表
clear() {
this.head = null;
this.size = 0;
}
// 转换为数组
toArray() {
const result = [];
let current = this.head;
while (current) {
result.push(current.data);
current = current.next;
}
return result;
}
// 遍历链表
forEach(callback) {
let current = this.head;
let index = 0;
while (current) {
callback(current.data, index);
current = current.next;
index++;
}
}
// 反转链表
reverse() {
let prev = null;
let current = this.head;
let next = null;
while (current) {
next = current.next; // 保存下一个节点
current.next = prev; // 反转指针
prev = current; // 移动 prev
current = next; // 移动 current
}
this.head = prev;
}
// 打印链表
toString() {
if (!this.head) {
return "Empty list";
}
const elements = [];
let current = this.head;
while (current) {
elements.push(current.data);
current = current.next;
}
return elements.join(" -> ");
}
}使用示例
// 创建链表
const list = new SinglyLinkedList();
// 添加元素
list.append(1);
list.append(2);
list.append(3);
list.prepend(0);
console.log(list.toString()); // "0 -> 1 -> 2 -> 3"
// 插入元素
list.insert(2, 1.5);
console.log(list.toString()); // "0 -> 1 -> 1.5 -> 2 -> 3"
// 查找元素
console.log(list.indexOf(2)); // 3
console.log(list.get(1)); // 1
console.log(list.contains(3)); // true
// 删除元素
list.remove(0); // 删除索引 0 的元素
list.removeByValue(1.5); // 删除值为 1.5 的元素
console.log(list.toString()); // "1 -> 2 -> 3"
// 反转链表
list.reverse();
console.log(list.toString()); // "3 -> 2 -> 1"
// 遍历链表
list.forEach((data, index) => {
console.log(`Index ${index}: ${data}`);
});关键算法细节
1. 插入操作中的指针变化
插入前:A -> B -> C
在 A 与 B 之间插入 X:
步骤 1:newNode.next = A.next
A -> B -> C
X ----↗
步骤 2:A.next = newNode
A -> X -> B -> C2. 删除操作中的指针变化
删除前:A -> B -> C -> D
删除 B:
步骤 1:找到 B 的前驱节点 A
步骤 2:A.next = B.next
A -----> C -> D
(B 被跳过,等待垃圾回收)3. 反转算法细节
// 反转过程示意
// 初始:1 -> 2 -> 3 -> null
// 目标:null <- 1 <- 2 <- 3
function reverse(head) {
let prev = null;
let current = head;
while (current !== null) {
let next = current.next; // 保存下一个节点
current.next = prev; // 反转当前节点的指针
prev = current; // prev 前移
current = next; // current 前移
}
return prev; // 此时 prev 指向新的头节点
}常见面试题
1. 检测链表中的环
function hasCycle(head) {
if (!head || !head.next) return false;
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
return true; // 发现环
}
}
return false;
}2. 找到链表的中点
function findMiddle(head) {
if (!head) return null;
let slow = head;
let fast = head;
while (fast.next && fast.next.next) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}3. 合并两个有序链表
function mergeTwoLists(l1, l2) {
const dummy = new ListNode(0);
let current = dummy;
while (l1 && l2) {
if (l1.data <= l2.data) {
current.next = l1;
l1 = l1.next;
} else {
current.next = l2;
l2 = l2.next;
}
current = current.next;
}
// 连接剩余的节点
current.next = l1 || l2;
return dummy.next;
}性能优化技巧
1. 尾指针优化
class OptimizedSinglyLinkedList {
constructor() {
this.head = null;
this.tail = null; // 维护尾指针
this.size = 0;
}
append(data) {
const newNode = new ListNode(data);
if (!this.head) {
this.head = this.tail = newNode;
} else {
this.tail.next = newNode;
this.tail = newNode;
}
this.size++;
}
// 现在 append 操作是 O(1) 而不是 O(n)
}2. 哨兵节点优化
class SentinelLinkedList {
constructor() {
this.sentinel = new ListNode(null); // 哨兵节点
this.sentinel.next = null;
this.size = 0;
}
// 有了哨兵节点后,许多操作的边界处理会更简单
prepend(data) {
const newNode = new ListNode(data);
newNode.next = this.sentinel.next;
this.sentinel.next = newNode;
this.size++;
}
}实际应用场景
1. 实现栈
class Stack {
constructor() {
this.list = new SinglyLinkedList();
}
push(item) {
this.list.prepend(item);
}
pop() {
return this.list.removeFirst();
}
peek() {
return this.list.isEmpty() ? null : this.list.get(0);
}
isEmpty() {
return this.list.isEmpty();
}
}2. 实现队列
class Queue {
constructor() {
this.head = null;
this.tail = null;
}
enqueue(item) {
const newNode = new ListNode(item);
if (!this.tail) {
this.head = this.tail = newNode;
} else {
this.tail.next = newNode;
this.tail = newNode;
}
}
dequeue() {
if (!this.head) return null;
const item = this.head.data;
this.head = this.head.next;
if (!this.head) this.tail = null;
return item;
}
}注意事项
- 空指针检查:总是先判断节点是否为空
- 边界情况:特别留意空链表和单节点的情况
- 内存管理:在需要手动管理内存的语言中,记得释放已删除的节点
- 指针操作顺序:插入和删除时注意指针修改的先后顺序
小结
单链表是理解链表这一数据结构的基础。虽然它在随机访问上不如数组高效,但在动态的插入与删除操作中表现出色。掌握单链表的实现与操作,是学习更复杂数据结构的必经之路。
下一节我们会学习双向链表,看看它如何通过增加反向指针带来更大的灵活性。
贡献者
这篇文章有帮助吗?
最近更新
Involution Hell© 2026 byCommunityunderCC BY-NC-SA 4.0