双链表
目录
单链表&&双链表:
单链表:无法逆向检索,有时候不方便
双链表:可进可退,存储密度更低一丢丢
一、双链表的定义与结构
定义:双链表的每个结点包含数据域`data`、前驱指针`prior`和后继指针`next`。
typedef struct DNode {
ElemType data; // 数据域
struct DNode *prior; // 前驱指针
struct DNode *next; // 后继指针
} DNode, *DLinklist;
与单链表的对比:
优势:支持双向遍历(前向和后向),可通过前驱指针快速访问前一结点。
劣势:每个结点多存储一个指针,存储密度略低于单链表。
二、双链表的基本操作
1. 初始化
操作目标:创建一个空的带头结点双链表。
实现逻辑:
头结点的`prior`和`next`均指向`NULL`。
空表判断:头结点的`next`为`NULL`时表示链表为空。
bool Empty(DLinklist L) {
return (L->next == NULL);
}
2. 插入操作(后插法)
目标:在结点`p`之后插入新结点`s`。
步骤:
1. 新结点`s`的`next`指向`p`的后继结点(`s->next = p->next`)。
2. 若`p`的后继结点存在,更新其后继结点的`prior`指向`s`(`p->next->prior = s`)。
3. `p`的`next`指向`s`(`p->next = s`)。
4. 更新`s`的`prior`指向`p`(`s->prior = p`)。
代码实现:
bool InsertNextDNode(DNode *p, DNode *s) {
if (p == NULL || s == NULL) return false;
s->next = p->next;
if (p->next != NULL) p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
边界处理:若`p`为最后一个结点,`p->next`为`NULL`,此时无需更新后继结点的`prior`。
3. 删除操作(删除后继结点)
目标:删除结点`p`的后继结点`q`。
步骤:
1. 找到`p`的后继结点`q`(`q = p->next`)。
2. 若`q`存在,将`p`的`next`指向`q`的后继结点(`p->next = q->next`)。
3. 若`q`的后继结点存在,更新其后继结点的`prior`指向`p`(`q->next->prior = p`)。
4. 释放`q`的内存空间(`free(q)`)。
代码实现:
bool DeleteNextDNode(DNode *p) {
if (p == NULL) return false;
DNode *q = p->next;
if (q == NULL) return false;
p->next = q->next;
if (q->next != NULL) q->next->prior = p;
free(q);
return true;
}
边界处理:若`q`为最后一个数据结点,`q->next`为`NULL`,无需更新前驱指针。
4. 遍历操作
前向遍历:从首元结点开始,通过`next`指针依次访问后续结点,终止条件为`p == NULL`。
while (p != NULL) {
// 处理结点p
p = p->next;
}
后向遍历:从尾结点开始,通过`prior`指针依次访问前驱结点,终止条件为`p == NULL`(需从尾结点逆向开始)。
while (p != NULL) {
// 处理结点p
p = p->prior;
}
三、关键特性与注意事项
1.双向性:双链表支持双向遍历,单链表仅能单向遍历。
2.指针修改顺序:插入和删除操作中,需先修改后继指针,再修改前驱指针,避免链表断裂。
3. 存储效率:每个结点额外存储一个指针,空间复杂度为`O(n)`,较单链表略高。
4. 查找操作:不支持随机存取,按位查找和按值查找均需遍历,时间复杂度为`O(n)`。
四、核心考点总结
初始化:头结点的指针域初始化为`NULL`。
插入/删除边界:处理最后一个结点时,需判断后继是否存在(`next == NULL`)。
遍历逻辑:前向遍历用`next`,后向遍历用`prior`,循环终止条件为指针指向`NULL`。
与单链表对比:双链表适合需要频繁双向访问的场景,单链表更节省存储空间。
通过双向指针设计,双链表在某些场景(如双向遍历、删除前驱结点)中效率更高,但实现复杂度略高于单链表。