链表

数组要求元素连续存储,容量固定或扩容时需要搬迁;链表由节点链接而成,节点含数据域和指针域,不要求连续存放。它以 O(1) 的已知位置插删能力,交换 O(1) 随机访问能力。

静态链表

竞赛中常用数组模拟指针:val[i] 保存数据,nxt[i] 保存后继下标,head=-1 表示空表,idx 指向下一个可用节点。已知节点下标时,头插、后插、删除后继均为 O(1)。

const int N = 100005;
int head = -1, idx = 0, val[N], nxt[N];
void addHead(int x) {
    val[idx] = x; nxt[idx] = head; head = idx++;
}
void insertAfter(int k, int x) {
    val[idx] = x; nxt[idx] = nxt[k]; nxt[k] = idx++;
}
void eraseAfter(int k) {
    if (nxt[k] != -1) nxt[k] = nxt[nxt[k]];
}
for (int p = head; p != -1; p = nxt[p]) cout << val[p] << ' ';

按位置访问和查找仍需遍历 O(n),且应检查数组容量和后继是否存在。

动态链表

struct Node {
    int val;
    Node* next;
    Node(int v, Node* n = nullptr) : val(v), next(n) {}
};
Node* head = nullptr;
head = new Node(5, head);  // 头插
// 删除节点后应 delete 被删除的节点

动态链表按需通过 new 创建节点;工程代码必须在删除节点后 delete,或使用智能指针管理所有权。带哨兵头节点可统一首节点和中间节点的插删逻辑。

变体与选择

双向链表增加 prev,可在已知节点时 O(1) 删除自身并支持双向遍历;循环链表让尾节点指向头节点,遍历应以回到起点为终止条件。数组适合随机访问和缓存友好的遍历,链表适合已知位置的频繁局部修改。