1. 单向链表基础概念解析
单向链表(Singly Linked List)是数据结构中最基础的链式存储结构之一,它由一系列节点组成,每个节点包含两个部分:数据域和指针域。数据域用于存储实际的数据元素,指针域则存储指向下一个节点的地址。与数组不同,链表中的元素在内存中不是连续存储的,而是通过指针相互连接。
单向链表的核心特点是:只能从头节点开始,沿着指针方向单向遍历,无法直接访问前驱节点。这个特性决定了它在操作上的诸多特点。
链表的第一个节点称为头节点(Head),最后一个节点的指针指向NULL,表示链表结束。相比数组,链表的主要优势在于插入和删除操作的高效性——不需要移动其他元素,只需修改相关节点的指针即可。但代价是失去了随机访问的能力,查找特定元素必须从头开始遍历。
在C语言中,单向链表的节点通常这样定义:
c复制struct Node {
int data; // 数据域
struct Node* next; // 指针域
};
而在C++中,可以使用类来定义:
cpp复制class Node {
public:
int data;
Node* next;
};
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 单向链表的创建与初始化
2.1 空链表的创建
创建一个空链表实际上就是创建一个头指针,并将其初始化为NULL:
c复制Node* head = NULL; // C/C++中创建空链表
在面向对象语言如Java中,可能这样表示:
java复制ListNode head = null; // Java中创建空链表
2.2 带初始节点的链表创建
更常见的情况是创建一个包含若干初始节点的链表。以创建包含三个节点的链表为例:
cpp复制// 创建三个独立节点
Node* first = new Node();
Node* second = new Node();
Node* third = new Node();
// 赋值并连接节点
first->data = 1;
first->next = second;
second->data = 2;
second->next = third;
third->data = 3;
third->next = NULL;
// 头指针指向第一个节点
Node* head = first;
这个过程在内存中的表现如下图所示(图示说明):
- 分配三个独立的内存块作为节点
- 设置各节点的data值
- 通过next指针连接节点形成链式结构
- 头指针指向链表的起始位置
2.3 链表的动态创建
实际应用中,链表通常是动态创建的。下面是一个通过用户输入动态创建链表的示例:
c复制Node* createLinkedList() {
Node *head = NULL, *current = NULL, *temp = NULL;
int value;
printf("输入一系列整数,以-1结束:\n");
while(1) {
scanf("%d", &value);
if(value == -1) break;
temp = (Node*)malloc(sizeof(Node));
temp->data = value;
temp->next = NULL;
if(head == NULL) {
head = temp;
current = temp;
} else {
current->next = temp;
current = current->next;
}
}
return head;
}
3. 单向链表的插入操作
3.1 链表插入的三种基本情况
在单向链表中,插入操作主要分为三种情况:
- 在链表头部插入(最简单)
- 在链表中间插入(需要找到前驱节点)
- 在链表尾部插入(需要遍历到末尾)
3.1.1 头部插入
头部插入是最简单的插入操作,只需要将新节点的next指向原头节点,然后更新头指针:
cpp复制void insertAtHead(Node** head_ref, int new_data) {
Node* new_node = new Node();
new_node->data = new_data;
new_node->next = *head_ref;
*head_ref = new_node;
}
注意这里使用了双指针(Node**),因为需要修改头指针本身。在C++中也可以使用引用(Node*&)来实现同样的效果。
3.1.2 尾部插入
尾部插入需要先遍历到链表末尾,然后将最后一个节点的next指向新节点:
cpp复制void insertAtTail(Node** head_ref, int new_data) {
Node* new_node = new Node();
new_node->data = new_data;
new_node->next = NULL;
if(*head_ref == NULL) {
*head_ref = new_node;
return;
}
Node* last = *head_ref;
while(last->next != NULL) {
last = last->next;
}
last->next = new_node;
}
3.1.3 中间插入(指定位置)
在指定位置插入节点需要先找到该位置的前驱节点:
cpp复制void insertAfter(Node* prev_node, int new_data) {
if(prev_node == NULL) {
printf("前驱节点不能为空");
return;
}
Node* new_node = new Node();
new_node->data = new_data;
new_node->next = prev_node->next;
prev_node->next = new_node;
}
3.2 插入操作的时间复杂度分析
- 头部插入:O(1),不需要遍历
- 尾部插入:O(n),需要遍历整个链表
- 指定位置插入:平均情况O(n),最坏情况O(n)
4. 单向链表的删除操作
4.1 删除节点的基本步骤
删除链表中的节点需要:
- 找到要删除的节点
- 找到它的前驱节点(单向链表无法直接获取前驱)
- 修改前驱节点的next指针,绕过要删除的节点
- 释放被删除节点的内存
4.2 删除指定值的节点
cpp复制void deleteNode(Node** head_ref, int key) {
Node* temp = *head_ref;
Node* prev = NULL;
// 如果要删除的是头节点
if(temp != NULL && temp->data == key) {
*head_ref = temp->next;
delete temp;
return;
}
// 查找要删除的节点及其前驱
while(temp != NULL && temp->data != key) {
prev = temp;
temp = temp->next;
}
// 如果没找到
if(temp == NULL) return;
// 从链表中移除节点
prev->next = temp->next;
delete temp;
}
4.3 删除指定位置的节点
cpp复制void deleteNodeAtPosition(Node** head_ref, int position) {
if(*head_ref == NULL) return;
Node* temp = *head_ref;
// 如果要删除的是头节点
if(position == 0) {
*head_ref = temp->next;
delete temp;
return;
}
// 找到要删除节点的前一个节点
for(int i=0; temp!=NULL && i<position-1; i++) {
temp = temp->next;
}
// 如果位置超出链表长度
if(temp == NULL || temp->next == NULL) return;
// temp->next是要删除的节点
Node* next = temp->next->next;
delete temp->next;
temp->next = next;
}
4.4 删除整个链表
cpp复制void deleteList(Node** head_ref) {
Node* current = *head_ref;
Node* next = NULL;
while(current != NULL) {
next = current->next;
delete current;
current = next;
}
*head_ref = NULL;
}
5. 单向链表的遍历与查找
5.1 基本遍历方法
遍历链表就是从头节点开始,依次访问每个节点,直到遇到NULL:
cpp复制void printList(Node* node) {
while(node != NULL) {
printf("%d ", node->data);
node = node->next;
}
printf("\n");
}
5.2 递归遍历
链表遍历也可以使用递归实现:
cpp复制void printListRecursive(Node* node) {
if(node == NULL) {
printf("\n");
return;
}
printf("%d ", node->data);
printListRecursive(node->next);
}
5.3 查找操作
查找链表中是否包含特定值:
cpp复制bool search(Node* head, int key) {
Node* current = head;
while(current != NULL) {
if(current->data == key) {
return true;
}
current = current->next;
}
return false;
}
5.4 获取链表长度
cpp复制int getLength(Node* head) {
int count = 0;
Node* current = head;
while(current != NULL) {
count++;
current = current->next;
}
return count;
}
6. 单向链表的常见问题与解决方案
6.1 内存泄漏问题
在C/C++中手动管理链表内存时,常见的错误是忘记释放删除的节点。解决方法:
- 确保每个delete/new配对
- 使用智能指针(C++11及以上)
- 实现析构函数自动释放内存
6.2 指针丢失问题
在插入和删除操作中,如果指针操作顺序不当,可能导致链表断裂。例如:
cpp复制// 错误的插入顺序
new_node->next = prev_node->next;
prev_node->next = new_node;
// 这才是正确的顺序
6.3 边界条件处理
链表操作需要特别注意以下边界条件:
- 空链表(head == NULL)
- 只有一个节点的链表
- 操作头节点
- 操作尾节点
- 无效的位置或值
6.4 调试技巧
调试链表问题时可以:
- 打印整个链表状态
- 使用调试器观察指针值
- 绘制链表结构图辅助理解
- 添加辅助计数器防止无限循环
7. 单向链表的应用实例
7.1 实现栈数据结构
利用链表的头部插入和删除操作可以实现高效的栈:
cpp复制class LinkedStack {
private:
Node* top;
public:
LinkedStack() : top(NULL) {}
void push(int value) {
Node* new_node = new Node();
new_node->data = value;
new_node->next = top;
top = new_node;
}
int pop() {
if(top == NULL) throw "Stack underflow";
int value = top->data;
Node* temp = top;
top = top->next;
delete temp;
return value;
}
bool isEmpty() {
return top == NULL;
}
};
7.2 多项式表示与相加
链表非常适合表示多项式,每个节点存储系数和指数:
cpp复制struct PolyNode {
int coeff;
int exp;
PolyNode* next;
};
PolyNode* addPolynomials(PolyNode* p1, PolyNode* p2) {
PolyNode dummy;
PolyNode* tail = &dummy;
while(p1 && p2) {
if(p1->exp > p2->exp) {
tail->next = new PolyNode{p1->coeff, p1->exp, NULL};
p1 = p1->next;
} else if(p1->exp < p2->exp) {
tail->next = new PolyNode{p2->coeff, p2->exp, NULL};
p2 = p2->next;
} else {
int sum = p1->coeff + p2->coeff;
if(sum != 0) {
tail->next = new PolyNode{sum, p1->exp, NULL};
}
p1 = p1->next;
p2 = p2->next;
}
if(tail->next) tail = tail->next;
}
tail->next = p1 ? p1 : p2;
return dummy.next;
}
7.3 LRU缓存实现
链表与哈希表结合可以实现高效的LRU缓存:
cpp复制class LRUCache {
private:
struct CacheNode {
int key;
int value;
CacheNode* prev;
CacheNode* next;
CacheNode(int k, int v) : key(k), value(v), prev(NULL), next(NULL) {}
};
unordered_map<int, CacheNode*> cache;
CacheNode* head;
CacheNode* tail;
int capacity;
void moveToHead(CacheNode* node) {
if(node == head) return;
if(node == tail) {
tail = node->prev;
tail->next = NULL;
} else {
node->prev->next = node->next;
node->next->prev = node->prev;
}
node->next = head;
head->prev = node;
node->prev = NULL;
head = node;
}
public:
LRUCache(int capacity) : capacity(capacity), head(NULL), tail(NULL) {}
int get(int key) {
if(cache.find(key) == cache.end()) return -1;
CacheNode* node = cache[key];
moveToHead(node);
return node->value;
}
void put(int key, int value) {
if(cache.find(key) != cache.end()) {
CacheNode* node = cache[key];
node->value = value;
moveToHead(node);
return;
}
if(cache.size() >= capacity) {
cache.erase(tail->key);
CacheNode* toDelete = tail;
tail = tail->prev;
if(tail) tail->next = NULL;
else head = NULL;
delete toDelete;
}
CacheNode* newNode = new CacheNode(key, value);
newNode->next = head;
if(head) head->prev = newNode;
head = newNode;
if(!tail) tail = head;
cache[key] = newNode;
}
};
8. 单向链表的变体与扩展
8.1 带头节点的单向链表
在基础单向链表前增加一个不存储数据的头节点,可以简化某些操作:
cpp复制class LinkedListWithDummyHead {
private:
Node* dummyHead;
public:
LinkedListWithDummyHead() {
dummyHead = new Node();
dummyHead->next = NULL;
}
void insert(int index, int value) {
Node* prev = dummyHead;
for(int i=0; i<index && prev->next; i++) {
prev = prev->next;
}
Node* new_node = new Node();
new_node->data = value;
new_node->next = prev->next;
prev->next = new_node;
}
};
8.2 循环单向链表
将尾节点的next指向头节点形成循环,适合环形缓冲区等应用:
cpp复制class CircularLinkedList {
private:
Node* tail;
public:
CircularLinkedList() : tail(NULL) {}
void insertAtEnd(int value) {
Node* new_node = new Node();
new_node->data = value;
if(tail == NULL) {
new_node->next = new_node;
tail = new_node;
} else {
new_node->next = tail->next;
tail->next = new_node;
tail = new_node;
}
}
};
8.3 静态链表
用数组实现的链表,适合没有指针的语言或特殊场景:
cpp复制#define MAX_SIZE 100
struct StaticNode {
int data;
int next; // 存储下一个节点的数组下标
};
class StaticLinkedList {
private:
StaticNode nodes[MAX_SIZE];
int head;
int free;
public:
StaticLinkedList() {
// 初始化所有节点为空闲状态
for(int i=0; i<MAX_SIZE-1; i++) {
nodes[i].next = i+1;
}
nodes[MAX_SIZE-1].next = -1;
head = -1;
free = 0;
}
void insert(int value) {
if(free == -1) throw "Out of space";
int new_node = free;
free = nodes[free].next;
nodes[new_node].data = value;
nodes[new_node].next = head;
head = new_node;
}
};
9. 单向链表的性能优化
9.1 尾指针优化
对于频繁在尾部插入的场景,可以维护一个尾指针:
cpp复制class LinkedListWithTail {
private:
Node* head;
Node* tail;
public:
LinkedListWithTail() : head(NULL), tail(NULL) {}
void insertAtTail(int value) {
Node* new_node = new Node();
new_node->data = value;
new_node->next = NULL;
if(tail == NULL) {
head = tail = new_node;
} else {
tail->next = new_node;
tail = new_node;
}
}
};
9.2 跳跃指针
在某些特定场景下,可以添加额外的指针来加速访问:
cpp复制class NodeWithSkip {
public:
int data;
NodeWithSkip* next;
NodeWithSkip* skip; // 指向后面第k个节点
NodeWithSkip(int val) : data(val), next(NULL), skip(NULL) {}
};
void buildSkipPointers(NodeWithSkip* head, int k) {
NodeWithSkip* current = head;
while(current != NULL) {
NodeWithSkip* temp = current;
for(int i=0; i<k && temp!=NULL; i++) {
temp = temp->next;
}
current->skip = temp;
current = current->next;
}
}
9.3 内存池优化
频繁的节点分配和释放可以通过内存池优化:
cpp复制class NodePool {
private:
vector<Node*> pool;
Node* freeList;
public:
Node* allocate(int data) {
if(freeList == NULL) {
Node* newBlock = new Node[100];
for(int i=0; i<99; i++) {
newBlock[i].next = &newBlock[i+1];
}
newBlock[99].next = NULL;
pool.push_back(newBlock);
freeList = newBlock;
}
Node* node = freeList;
freeList = freeList->next;
node->data = data;
node->next = NULL;
return node;
}
void deallocate(Node* node) {
node->next = freeList;
freeList = node;
}
~NodePool() {
for(auto block : pool) {
delete[] block;
}
}
};
10. 单向链表的高级应用
10.1 链表排序
10.1.1 归并排序实现
cpp复制Node* mergeSort(Node* head) {
if(head == NULL || head->next == NULL) {
return head;
}
// 使用快慢指针找到中点
Node* slow = head;
Node* fast = head->next;
while(fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
Node* second = slow->next;
slow->next = NULL;
// 递归排序两个子链表
Node* left = mergeSort(head);
Node* right = mergeSort(second);
// 合并两个有序链表
return merge(left, right);
}
Node* merge(Node* a, Node* b) {
Node dummy;
Node* tail = &dummy;
while(a != NULL && b != NULL) {
if(a->data <= b->data) {
tail->next = a;
a = a->next;
} else {
tail->next = b;
b = b->next;
}
tail = tail->next;
}
tail->next = (a != NULL) ? a : b;
return dummy.next;
}
10.2 链表反转
10.2.1 迭代法反转
cpp复制Node* reverseIterative(Node* head) {
Node* prev = NULL;
Node* current = head;
Node* next = NULL;
while(current != NULL) {
next = current->next;
current->next = prev;
prev = current;
current = next;
}
return prev;
}
10.2.2 递归法反转
cpp复制Node* reverseRecursive(Node* head) {
if(head == NULL || head->next == NULL) {
return head;
}
Node* rest = reverseRecursive(head->next);
head->next->next = head;
head->next = NULL;
return rest;
}
10.3 检测链表环
使用快慢指针检测链表中是否存在环:
cpp复制bool hasCycle(Node* head) {
if(head == NULL) return false;
Node* slow = head;
Node* fast = head->next;
while(fast != NULL && fast->next != NULL) {
if(slow == fast) return true;
slow = slow->next;
fast = fast->next->next;
}
return false;
}
10.4 找到环的起点
cpp复制Node* detectCycleStart(Node* head) {
if(head == NULL) return NULL;
Node* slow = head;
Node* fast = head;
bool hasCycle = false;
while(fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
if(slow == fast) {
hasCycle = true;
break;
}
}
if(!hasCycle) return NULL;
slow = head;
while(slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
11. 单向链表与其他数据结构的比较
11.1 链表 vs 数组
| 特性 | 数组 | 单向链表 |
|---|---|---|
| 内存分配 | 连续内存块 | 非连续内存 |
| 大小 | 固定大小 | 动态增长 |
| 访问方式 | 随机访问O(1) | 顺序访问O(n) |
| 插入/删除 | O(n)需要移动元素 | O(1)已知位置 |
| 内存使用 | 无额外开销 | 每个节点需要指针空间 |
| 缓存友好性 | 高(空间局部性) | 低 |
11.2 链表 vs 双向链表
| 特性 | 单向链表 | 双向链表 |
|---|---|---|
| 指针数量 | 每个节点1个指针 | 每个节点2个指针 |
| 内存占用 | 较小 | 较大 |
| 遍历方向 | 只能单向 | 可以双向 |
| 删除节点 | 需要知道前驱节点 | 可以直接删除当前节点 |
| 插入复杂度 | O(n)找到前驱节点 | O(1)已知节点 |
11.3 链表 vs 动态数组
动态数组(如C++的vector)在大多数情况下是更好的选择,除非:
- 需要频繁在头部或中间插入/删除
- 内存碎片是主要关注点
- 需要确定性的插入/删除时间
- 数据量非常大且增长不可预测
12. 不同语言中的链表实现差异
12.1 C语言实现
C语言需要手动管理内存,节点通常定义为结构体:
c复制typedef struct Node {
int data;
struct Node* next;
} Node;
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
12.2 C++实现
C++可以使用类和智能指针简化内存管理:
cpp复制class LinkedList {
private:
struct Node {
int data;
std::unique_ptr<Node> next;
Node(int val) : data(val), next(nullptr) {}
};
std::unique_ptr<Node> head;
public:
void insert(int value) {
auto newNode = std::make_unique<Node>(value);
newNode->next = std::move(head);
head = std::move(newNode);
}
};
12.3 Java实现
Java使用引用和垃圾回收,实现更简洁:
java复制class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
class LinkedList {
ListNode head;
public void add(int val) {
ListNode newNode = new ListNode(val);
newNode.next = head;
head = newNode;
}
}
12.4 Python实现
Python的实现更为简单,但原理相同:
python复制class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, data):
new_node = Node(data)
if self.head is None:
self.head = new_node
return
last = self.head
while last.next:
last = last.next
last.next = new_node
13. 单向链表的实际工程应用
13.1 文件系统实现
许多文件系统使用链表结构来管理磁盘块。例如FAT文件系统使用类似链表的结构来跟踪文件占用的簇:
c复制struct FATEntry {
int nextCluster; // 下一个簇号,类似链表中的next指针
bool isEOF; // 是否文件结束
};
13.2 内存管理
操作系统中的空闲内存块管理常使用链表结构:
c复制struct MemBlock {
size_t size;
bool isFree;
MemBlock* next;
};
13.3 哈希表冲突解决
哈希表中解决冲突的链地址法就是使用链表:
cpp复制class HashTable {
private:
static const int TABLE_SIZE = 100;
Node* table[TABLE_SIZE];
int hashFunction(int key) {
return key % TABLE_SIZE;
}
public:
void insert(int key) {
int index = hashFunction(key);
Node* newNode = new Node(key);
newNode->next = table[index];
table[index] = newNode;
}
};
13.4 图形用户界面
GUI系统中的控件层次结构常使用链表来管理子控件:
cpp复制class Widget {
private:
Widget* firstChild;
Widget* nextSibling;
// ...
public:
void addChild(Widget* child) {
child->nextSibling = firstChild;
firstChild = child;
}
};
14. 单向链表的测试与验证
14.1 单元测试框架
为链表实现编写全面的单元测试:
cpp复制#include <cassert>
void testLinkedList() {
// 测试空链表
Node* list = NULL;
assert(getLength(list) == 0);
// 测试插入
insertAtHead(&list, 1);
assert(getLength(list) == 1);
assert(list->data == 1);
insertAtTail(&list, 2);
assert(getLength(list) == 2);
assert(list->next->data == 2);
// 测试删除
deleteNode(&list, 1);
assert(getLength(list) == 1);
assert(list->data == 2);
// 测试查找
assert(search(list, 2) == true);
assert(search(list, 1) == false);
// 清理
deleteList(&list);
assert(list == NULL);
}
int main() {
testLinkedList();
printf("所有测试通过!\n");
return 0;
}
14.2 内存泄漏检测
在C/C++中可以使用工具或手动方法检测内存泄漏:
cpp复制void testMemoryLeak() {
int initialAllocations = getAllocationCount(); // 自定义函数获取当前分配次数
Node* list = NULL;
for(int i=0; i<100; i++) {
insertAtHead(&list, i);
}
deleteList(&list);
int finalAllocations = getAllocationCount();
assert(initialAllocations == finalAllocations);
}
14.3 性能测试
测试链表操作的时间复杂度是否符合预期:
cpp复制#include <chrono>
void testPerformance() {
Node* list = NULL;
// 测试插入性能
auto start = std::chrono::high_resolution_clock::now();
for(int i=0; i<10000; i++) {
insertAtHead(&list, i);
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = end-start;
printf("头插入10000个元素耗时: %f秒\n", diff.count());
// 测试查找性能
start = std::chrono::high_resolution_clock::now();
for(int i=0; i<1000; i++) {
search(list, i);
}
end = std::chrono::high_resolution_clock::now();
diff = end-start;
printf("查找1000次耗时: %f秒\n", diff.count());
deleteList(&list);
}
15. 单向链表的可视化与调试技巧
15.1 文本可视化
实现一个简单的链表可视化函数:
cpp复制void visualizeList(Node* head) {
printf("链表结构:\n");
while(head != NULL) {
printf("[%d]", head->data);
if(head->next != NULL) {
printf(" -> ");
}
head = head->next;
}
printf(" -> NULL\n");
}
示例输出:
code复制链表结构:
[1] -> [2] -> [3] -> NULL
15.2 图形化可视化
可以使用Graphviz生成链表图形:
cpp复制void generateDotFile(Node* head, const char* filename) {
FILE* fp = fopen(filename, "w");
fprintf(fp, "digraph G {\n");
fprintf(fp, " rankdir=LR;\n");
fprintf(fp, " node [shape=record];\n");
while(head != NULL) {
fprintf(fp, " node%p [label=\"{%d|%p}\"];\n",
head, head->data, head->next);
if(head->next != NULL) {
fprintf(fp, " node%p -> node%p;\n", head, head->next);
} else {
fprintf(fp, " node%p -> NULL;\n", head);
}
head = head->next;
}
fprintf(fp, " NULL [shape=plaintext];\n");
fprintf(fp, "}\n");
fclose(fp);
}
15.3 调试技巧
- 打印指针值:调试时打印节点地址有助于理解链表结构
- 添加哨兵节点:临时添加特殊节点标记位置
- 绘制示意图:在纸上画出链表结构
- 使用调试器:设置条件断点观察特定节点
- 边界测试:专门测试空链表、单节点链表等情况
16. 单向链表的常见面试题
16.1 合并两个有序链表
cpp复制Node* mergeTwoLists(Node* l1, Node* l2) {
Node dummy;
Node* tail = &dummy;
while(l1 && l2) {
if(l1->data <= l2->data) {
tail->next = l1;
l1 = l1->next;
} else {
tail->next = l2;
l2 = l2->next;
}
tail = tail->next;
}
tail->next = l1 ? l1 : l2;
return dummy.next;
}
16.2 删除链表倒数第N个节点
cpp复制Node* removeNthFromEnd(Node* head, int n) {
Node dummy;
dummy.next = head;
Node *fast = &dummy, *slow = &dummy;
for(int i=0; i<=n; i++) {
fast = fast->next;
}
while(fast) {
fast = fast->next;
slow = slow->next;
}
Node* toDelete = slow->next;
slow->next = slow->next->next;
delete toDelete;
return dummy.next;
}
16.3 判断回文链表
cpp复制bool isPalindrome(Node* head) {
if(head == NULL || head->next == NULL) return true;
// 找到中点
Node *slow = head, *fast = head;
while(fast->next && fast->next->next) {
slow = slow->next;
fast = fast->next->next;
}
// 反转后半部分
Node* secondHalf = reverseIterative(slow->next);
Node* p1 = head;
Node* p2 = secondHalf;
// 比较两部分
bool result = true;
while(result && p2) {
if(p1->data != p2->data) {
result = false;
}
p1 = p1->next;
p2 = p2->next;
}
// 恢复链表
slow->next = reverseIterative(secondHalf);
return result;
}
16.4 链表相交问题
cpp复制Node* getIntersectionNode(Node* headA, Node* headB) {
if(headA == NULL || headB == NULL) return NULL;
Node *a = headA, *b = headB;
while(a != b) {
a = a == NULL ? headB : a->next;
b = b == NULL ? headA : b->next;
}
return a;
}
17. 单向链表的进阶话题
17.1 函数式编程中的链表
在函数式语言如Haskell中,链表是基础数据结构:
haskell复制data List a = Empty | Cons a (List a)
-- 创建链表
let myList = Cons 1 (Cons 2 (Cons 3 Empty))
-- 映射操作
mapList :: (a -> b) -> List a -> List b
mapList _ Empty = Empty
mapList f (Cons x xs) = Cons (f x) (mapList f xs)
-- 过滤操作
filterList :: (a -> Bool) -> List a -> List a
filterList _ Empty = Empty
filterList p (Cons x xs)
| p x = Cons x (filterList p xs)
| otherwise = filterList p xs
17.2 并发链表设计
线程安全的链表实现需要考虑同步问题:
java复制public class ConcurrentLinkedList<T> {
private Node<T> head;
private final Object lock = new Object();
private static class Node<T> {
T item;
Node<T> next;
}
public void add(T item) {
synchronized(lock) {
Node<T> newNode = new Node<>();
newNode.item = item;
newNode.next = head;
head = newNode;
}
}
public boolean contains(T item) {
synchronized(lock) {
Node<T> current = head;
while(current != null) {
if(item.equals(current.item)) {
return true;
}
current = current.next;
}
return false;
}
}
}
17.3 持久化链表
不可变链表支持高效版本控制:
java复制public class PersistentList<T>
