链表作为数据结构中的经典类型,本质上是由一系列节点组成的线性集合。每个节点包含两个部分:数据域(存储实际数据)和指针域(存储下一个节点的地址)。与数组的连续内存分配不同,链表的节点在内存中可以是非连续分布的,这种特性带来了独特的优势和使用场景。
链表的几种基本类型需要明确区分:
关键理解:链表通过指针链接实现的动态结构,使其在插入/删除操作上具有O(1)时间复杂度优势,但随机访问需要O(n)遍历时间。
以C++为例,规范的链表节点定义应包含构造函数初始化:
cpp复制struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
Python实现则通常采用类形式:
python复制class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
| 操作 | 数组 | 链表 |
|---|---|---|
| 随机访问 | O(1) | O(n) |
| 头部插入 | O(n) | O(1) |
| 尾部插入 | O(1) | O(n) |
| 已知位置插入 | O(n) | O(1) |
| 元素查找 | O(n) | O(n) |
处理链表问题时,引入dummy节点可极大简化边界条件处理。例如删除操作:
cpp复制ListNode* removeElements(ListNode* head, int val) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* cur = dummy;
while (cur->next) {
if (cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
} else {
cur = cur->next;
}
}
return dummy->next;
}
python复制def middleNode(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
在修改指针指向时,必须注意操作顺序。例如反转链表时:
cpp复制ListNode* reverseList(ListNode* head) {
ListNode *prev = nullptr;
ListNode *curr = head;
while (curr) {
ListNode *nextTemp = curr->next; // 必须先保存next节点
curr->next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
通过建立多级索引提升查找效率:
code复制1 -> 4 -> 6 -> 9 -> 12 -> 15 -> 19 -> 23
1 ------------> 9 ------------> 19
1 ----------------------------> 23
在大规模数据场景下,可采用标记删除而非立即移除节点,定期批量处理
结合哈希表和双向链表的典型应用:
python复制class LRUCache:
def __init__(self, capacity):
self.cache = {}
self.capacity = capacity
self.head = ListNode()
self.tail = ListNode()
self.head.next = self.tail
self.tail.prev = self.head
def _add_node(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def _remove_node(self, node):
prev = node.prev
new = node.next
prev.next = new
new.prev = prev
递归解法体现链表操作的优雅性:
java复制public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
使用哈希表存储原节点与拷贝节点的映射关系:
python复制def copyRandomList(head):
if not head: return None
mapping = {}
curr = head
while curr:
mapping[curr] = Node(curr.val)
curr = curr.next
curr = head
while curr:
mapping[curr].next = mapping.get(curr.next)
mapping[curr].random = mapping.get(curr.random)
curr = curr.next
return mapping[head]
开发时建议实现专用打印工具:
cpp复制void printList(ListNode* head) {
while (head) {
cout << head->val;
if (head->next) cout << "->";
head = head->next;
}
cout << endl;
}
频繁创建/删除节点时,可预分配节点池:
cpp复制class ListNodePool {
vector<ListNode*> pool;
public:
ListNode* allocate(int val) {
if (pool.empty()) return new ListNode(val);
ListNode* node = pool.back();
pool.pop_back();
node->val = val;
node->next = nullptr;
return node;
}
void deallocate(ListNode* node) {
pool.push_back(node);
}
};
将节点存储在连续内存中提升缓存命中率:
c复制struct CompactList {
int* values;
int* next_indices;
int capacity;
int head;
};
Rust的链表实现需要特别注意所有权:
rust复制impl<T> List<T> {
pub fn push(&mut self, elem: T) {
let new_node = Box::new(Node {
elem,
next: self.head.take(),
});
self.head = Some(new_node);
}
}
Java标准库实现的双向链表采用节点嵌套类:
java复制private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}