1. 什么是链表?
链表(Linked List)是一种 线性数据结构,由一系列 节点(Node) 组成,每个节点包含 数据域 和 指针域。链表中的节点在内存中 不一定连续存储,通过指针连接。
链表的特点
-
动态大小:链表的大小可以动态调整。
-
非连续存储:节点在内存中不一定连续存储。
-
插入和删除高效:在链表中插入或删除节点的时间复杂度为 O(1)O(1)(已知位置)。
2. 链表的类型
2.1 单链表(Singly Linked List)
-
每个节点只有一个指针,指向下一个节点。
-
最后一个节点的指针指向
nullptr
。
2.2 双向链表(Doubly Linked List)
-
每个节点有两个指针,分别指向前一个节点和后一个节点。
-
支持双向遍历。
2.3 循环链表(Circular Linked List)
-
单链表或双向链表的变种,最后一个节点的指针指向头节点。
3. 链表的基本操作
3.1 定义节点
-
单链表节点:
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
双向链表节点:
struct ListNode {
int val;
ListNode* prev;
ListNode* next;
ListNode(int x) : val(x), prev(nullptr), next(nullptr) {}
};
3.2 创建链表
-
手动创建链表:
ListNode* head = new ListNode(1);
head->next = new ListNode(2);
head->next->next = new ListNode(3);
3.3 遍历链表
-
使用循环遍历链表:
void printList(ListNode* head) {
ListNode* curr = head;
while (curr != nullptr) {
cout << curr->val << " ";
curr = curr->next;
}
}
3.4 插入节点
-
在链表头部插入节点:
void insertAtHead(ListNode*& head, int val) {
ListNode* newNode = new ListNode(val);
newNode->next = head;
head = newNode;
}
在链表尾部插入节点:
void insertAtTail(ListNode*& head, int val) {
ListNode* newNode = new ListNode(val);
if (head == nullptr) {
head = newNode;
return;
}
ListNode* curr = head;
while (curr->next != nullptr) {
curr = curr->next;
}
curr->next = newNode;
}
3.5 删除节点
-
删除指定值的节点:
void deleteNode(ListNode*& head, int val) {
if (head == nullptr) return;
if (head->val == val) {
ListNode* temp = head;
head = head->next;
delete temp;
return;
}
ListNode* curr = head;
while (curr->next != nullptr && curr->next->val != val) {
curr = curr->next;
}
if (curr->next != nullptr) {
ListNode* temp = curr->next;
curr->next = curr->next->next;
delete temp;
}
}
4. 链表的优缺点
4.1 优点
-
动态大小:链表的大小可以动态调整。
-
插入和删除高效:在链表中插入或删除节点的时间复杂度为 O(1)O(1)(已知位置)。
-
内存利用率高:节点在内存中不一定连续存储,适合动态分配内存。
4.2 缺点
-
随机访问效率低:访问链表中的元素需要从头遍历,时间复杂度为 O(n)O(n)。
-
额外空间开销:每个节点需要额外的指针空间。
5. 链表的常见问题
5.1 反转链表
-
使用迭代法反转链表:
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr != nullptr) {
ListNode* next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
5.2 检测链表是否有环
-
使用快慢指针检测环:
bool hasCycle(ListNode* head) {
if (head == nullptr) return false;
ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) return true;
}
return false;
}
5.3 合并两个有序链表
-
递归合并两个有序链表:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == nullptr) return l2;
if (l2 == nullptr) return l1;
if (l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
} else {
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
6. 链表的应用场景
-
实现动态数据结构:链表可以用于实现栈、队列、哈希表等数据结构。
-
内存管理:链表适合动态分配内存的场景。
-
图算法:链表可以用于表示图的邻接表。
7. 总结
链表是一种灵活的数据结构,具有 动态大小 和 高效插入删除 的特点。尽管链表的随机访问效率较低,但它在许多场景中仍然是不可替代的。