双链表(Doubly Linked List)在单链表基础上增加了一个前驱指针 prior,使得每个结点可以双向遍历。
| 特性 | 单链表 | 双链表 |
|---|---|---|
| 指针数 | 1 个(next) | 2 个(prior, next) |
| 遍历方向 | 仅后向 | 前向 + 后向 |
| 删除已知结点 | O(n)(需找前驱) | O(1) |
| 空间开销 | 较小 | 较大(多一个指针) |
| 插入操作 | 需要注意顺序 | 需要注意顺序 |
typedef struct DNode {
int data; // 数据域
struct DNode *prior; // 前驱指针
struct DNode *next; // 后继指针
} DNode, *DLinkList;
NULL ← [A] ⇄ [B] ⇄ [C] ⇄ [D] → NULL
↑prior ↑data ↑next
(头结点 prior=NULL,尾结点 next=NULL)
| 操作 | 时间复杂度 | 关键点 |
|---|---|---|
| 按位查找 | O(n) | 从头遍历 |
| 按值查找 | O(n) | 从头遍历 |
| 指定结点后插 | O(1) | 直接操作 |
| 指定结点前插 | O(1) | 直接操作(vs 单链表 O(n)) |
| 删除指定结点 | O(1) | 直接操作(vs 单链表 O(n)) |
s->next = p->next; // ① s 的后继指向 p 的后继
s->prior = p; // ② s 的前驱指向 p
p->next->prior = s; // ③ p 的后继的前驱指向 s(需判断 p->next 是否 NULL)
p->next = s; // ④ p 的后继指向 s
插入前: ... ⇄ [p] ⇄ [q] ⇄ ...
插入后: ... ⇄ [p] ⇄ [s] ⇄ [q] ⇄ ...
p->next 被覆盖,找不到原后继结点。// 方法1:利用 prior 指针
s->prior = p->prior; // ① s 的前驱指向 p 的前驱
s->next = p; // ② s 的后继指向 p
p->prior->next = s; // ③ p 的前驱的后继指向 s
p->prior = s; // ④ p 的前驱指向 s
// 方法2:转化为后插——在 p->prior 后面插入 s
p->prior->next = p->next; // ① p 的前驱的后继指向 p 的后继
p->next->prior = p->prior; // ② p 的后继的前驱指向 p 的前驱
free(p); // ③ 释放 p
p->next 为 NULL,步骤②需特判。插入1: [HEAD] ⇄ [1] → NULL
插入2: [HEAD] ⇄ [2] ⇄ [1] → NULL
插入3: [HEAD] ⇄ [3] ⇄ [2] ⇄ [1] → NULL
结果: 3 → 2 → 1(逆序)
void DeleteAllX(DLinkList L, int x) {
DNode *p = L->next;
while (p != NULL) {
if (p->data == x) {
DNode *q = p;
p->prior->next = p->next;
if (p->next != NULL) // 不是尾结点
p->next->prior = p->prior;
p = p->next;
free(q);
} else {
p = p->next;
}
}
}
p->next->prior 前需判断 p->next 是否为 NULL。free:仅改指针不释放内存会导致内存泄漏。↑ 以上为站内 HTML 相对链接(纯网页可浏览);本页右上「在 Obsidian 中打开」跳回源笔记。