1.双向链表定义
顾名思义,在双向链表的结点中有两个指针域,其一指向直接后继,另一指向直接前驱。
2.特点
| 项目 | 内容 |
|---|---|
| 定义 | 一种链式存储结构,每个结点包含 数据域 、前驱指针域(prio) 和 后继指针域(next) |
| 指针数量 | 每个结点有两个指针:一个指向前驱结点,一个指向后继结点 |
| 存储方式 | 逻辑上连续,物理存储不要求连续 |
| 访问方向 | 可以沿着 next 向后遍历,也可以沿着 prev 向前遍历 |
| 头结点特点 | 常设一个头结点,prio = NULL,方便统一操作 |
| 尾结点特点 | 最后一个结点的 next = NULL |
3.双向链表的优缺点
| 优点 | 缺点 |
|---|---|
| 支持双向遍历,查找更灵活 | 每个结点需要额外存储一个前驱指针,空间开销大 |
| 删除、插入时,不必只依赖单方向指针 | 插入、删除时要维护两个指针,操作稍复杂 |
| 可以在 O(1) 时间找到前驱结点 | 实现比单链表复杂 |
4.实现代码
申请节点
cpp
Node* _buynode(ElemType x)
{
Node* s = (Node*)malloc(sizeof(Node));
assert(s != NULL);
s->data = x;
s->next = s->prio = NULL;
return s;
}
目的:申请并初始化一个新节点(数据域赋值,前后指针置空)。
实现步骤:
malloc(sizeof(Node)) 分配内存给一个 Node。
assert(s != NULL) 检查分配是否成功(失败时程序中止)。
s->data = x:把数据放入节点。
s->next = s->prio = NULL:初始化前驱和后继指针为空(后续插入时再设置指针)。
返回新节点指针。
时间复杂度:O(1)。
初始化
cpp
void InitDList(List* list)
{
Node* s = (Node*)malloc(sizeof(Node));
assert(s != NULL);
list->first = list->last = s;
list->last->next = NULL;
list->first->prio = NULL;
list->size = 0;
}

目的:初始化链表(创建头结点、设定空表状态)。
实现步骤:
分配一个头结点 s。
把 list->first 和 list->last 都指向头结点(表示空表)。
list->last->next = NULL:头结点的 next 置 NULL(空表无有效节点)。
list->first->prio = NULL:头结点 prio 为 NULL。
list->size = 0:长度置 0。
时间复杂度:O(1)。
尾插
cpp
void push_back(List* list, ElemType x)
{
Node* s = _buynode(x);
s->prio = list->last;
list->last->next = s;
list->last = s;
list->size++;
}

目的:尾插一个新节点,把 x 放到链表末尾。
实现步骤:
创建新节点 s(next/prio 初始为 NULL)。
s->prio = list->last;:把新节点的前驱指向当前尾节点(头结点或某个有效节点)。
list->last->next = s;:旧尾节点的 next 指向新节点(若为空表则 list->last 为头结点)。
list->last = s;:把 last 更新为新节点。
list->size++:长度加一。
时间复杂度:O(1)。
头插
cpp
void push_front(List* list, ElemType x)
{
Node* s = _buynode(x);
if (list->first == list->last)
{
// empty list
list->last = s;
}
else
{
s->next = list->first->next;
s->next->prio = s;
}
s->prio = list->first;
list->first->next = s;
list->size++;
}

目的:头插,把 x 插入到第一个有效节点之前(即头结点之后)。
实现步骤:
新建节点 s。
判断是否为空表(list->first == list->last):
空表:把 list->last = s;(新节点成为尾节点)。注意头结点的 next 还未设置。
非空:把 s->next = list->first->next;,并把原第一个节点的 prio 更新为 s。
s->prio = list->first;:新节点的前驱指向头结点。
list->first->next = s;:头结点的 next 指向新节点(完成插入)。
list->size++。
时间复杂度:O(1)。
显示数
cpp
void show_list(List* list)
{
Node* p = list->first->next;
while (p != NULL)
{
printf("%d-->", p->data);
p = p->next;
}
printf("Nul.\n");
}
目的:从头到尾遍历并打印所有有效节点的数据。
实现步骤:
从 list->first->next(第一个有效节点或 NULL)开始。
循环 while (p != NULL):打印 p->data,然后 p = p->next。
遍历结束打印结束标识。
时间复杂度:O(n)。
尾删
cpp
void pop_back(List* list)
{
if (list->size == 0)
return;
Node* p = list->first;
while (p->next != list->last)
p = p->next;
free(list->last);
list->last = p;
list->last->next = NULL;
list->size--;
}

目的:删除尾节点(最后一个有效节点)。
实现步骤:
如果空表直接返回。
从头结点 p = list->first 开始,遍历找到尾节点的前驱(找到 p 使 p->next == list->last)。
free(list->last):释放尾节点内存。
list->last = p:把前驱设为新的尾节点。
list->last->next = NULL:新的尾节点 next 置 NULL。
list->size--。
时间复杂度:O(n)(实现中用了线性查找前驱)。
头删
cpp
void pop_front(List* list)
{
if (list->size == 0)
return;
Node* p = list->first->next;
if (list->first->next == list->last)
{
list->last = list->first;
list->last->next = NULL;
}
else
{
p->next->prio = list->first;
list->first->next = p->next;
}
free(p);
list->size--;
}

目的:删除第一个有效节点(头结点之后的节点)。
实现步骤:
空表返回。
p = list->first->next:指向要删除的节点。
如果只有一个有效节点(first->next == last):
把 list->last = list->first;(变为空表)。
list->last->next = NULL。
否则(有多个节点):
把第二个节点的 prio 指向头结点(p->next->prio = list->first)。
把头结点的 next 指向第二个节点(list->first->next = p->next)。
free(p) 并 size--。
时间复杂度:O(1)。
插入值
cpp
void insert_val(List* list, ElemType x)
{
Node* p = list->first;
while (p->next != NULL && p->next->data < x)
p = p->next;
if (p->next == NULL)
{
push_back(list, x);
}
else
{
Node* s = _buynode(x);
s->next = p->next;
s->next->prio = s;
s->prio = p;
p->next = s;
list->size++;
}
}

目的:按升序把 x 插入到合适位置(保持链表有序)。
实现步骤:
从头结点 p = first 开始,找到第一个 p 使得 p->next->data >= x 或者到达尾(p->next == NULL)。
如果到达尾(p->next == NULL),调用 push_back(尾插)。
否则在 p 与 p->next 之间插入新节点 s:
s->next = p->next;
s->next->prio = s;(设置后继的前驱指针)
s->prio = p;
p->next = s;
size++。
时间复杂度:O(n)(需要顺序查找插入位置)。
查找
cpp
Node* find(List* list, ElemType key)
{
Node* p = list->first->next;
while (p != NULL && p->data != key)
p = p->next;
return p;
}
目的:查找第一个值为 key 的节点并返回其指针,找不到返回 NULL。
实现步骤:
从 list->first->next 开始线性查找,直到 p==NULL 或 p->data==key。
返回 p(可能为 NULL 或找到的节点)。
时间复杂度:O(n)。
长度
cpp
int length(List* list)
{
return list->size;
}
删除值
cpp
void delete_val(List* list, ElemType key)
{
if (list->size == 0)
return;
Node* p = find(list, key);
if (p == NULL)
{
printf("此数据不存在。\n");
return;
}
if (p == list->last)
{
list->last = p->prio;
list->last->next = NULL;
}
else
{
p->next->prio = p->prio;
p->prio->next = p->next;
}
free(p);
list->size--;
}

目的:删除值等于 key 的节点(第一个匹配的节点),并释放内存。
实现步骤:
空表返回。
p = find(...) 查找目标节点;若 NULL 则提示并返回。
若 p 是尾节点:把 list->last 更新为 p->prio 并把新的 last->next = NULL。
否则(中间节点):把 p->next->prio 指向 p->prio;把 p->prio->next 指向 p->next(断开 p)。
free(p) 并 size--。
时间复杂度:O(n)(包含 find 的时间)。
排序
cpp
void sort(List* list)
{
if (list->size == 0 || list->size == 1)
return;
Node* s = list->first->next;
Node* q = s->next;
list->last = s;
list->last->next = NULL;
while (q != NULL)
{
s = q;
q = q->next;
Node* p = list->first;
while (p->next != NULL && p->next->data < s->data)
p = p->next;
if (p->next == NULL)
{
s->next = NULL;
s->prio = list->last;
list->last->next = s;
list->last = s;
}
else
{
s->next = p->next;
s->next->prio = s;
s->prio = p;
p->next = s;
}
}
}

目的:对链表按 data 升序排序(实现为链式插入排序)。
实现步骤(算法思想):
特殊情况:0 或 1 个节点直接返回。
以链表的第一个节点 s 作为已排序部分(初始只有一个节点),q = s->next 指向未处理部分的第一个节点。
设置 list->last = s; list->last->next = NULL;------先把链表当作以 first 头结点和 s 作为已排序子链。
注意:这里并没有把 list->first->next 修改(它本来就是 s)。
进入主循环:对未排序节点 q 逐个取出为 s,并在已排序子链中找到合适位置 p(从头结点开始),然后插入 s:
若 p->next == NULL(插到末尾):把 s->next = NULL, s->prio = list->last; list->last->next = s; list->last = s;
否则插到 p 与 p->next 之间,同时更新 s->next, s->prio, p->next->prio, p->next。
循环直到 q == NULL(所有节点插完)。
时间复杂度:O(n²)(典型插入排序,最坏时每次都需遍历已排序序列)。
逆序
cpp
void resver(List* list)
{
if (list->size == 0 || list->size == 1)
return;
Node* p = list->first->next;
Node* q = p->next;
list->last = p;
list->last->next = NULL;
while (q != NULL)
{
p = q;
q = q->next;
p->next = list->first->next;
p->next->prio = p;
p->prio = list->first;
list->first->next = p;
}
}
目的:反转链表中有效节点(把原来顺序反转,头结点之后变为原尾)。
实现步骤(头插法反转):
空表或单节点直接返回。
p = first->next(原第一个),q = p->next(原第二)。
把 list->last = p; list->last->next = NULL;(最初把第一个节点设为新尾)。
然后对余下每个节点 q:
把 p = q; q = q->next;
把 p 插到头结点之后:p->next = first->next; p->next->prio = p; p->prio = first; first->next = p;
循环直到所有节点移动到头部顺序反转完成。
时间复杂度:O(n)。
清除
cpp
void clear(List* list)
{
if (list->size == 0)
return;
Node* p = list->first->next;
while (p != NULL)
{
if (p == list->last)
{
list->last = list->first;
list->last->next = NULL;
}
else
{
p->next->prio = list->first;
list->first->next = p->next;
}
free(p);
p = list->first->next;
}
list->size = 0;
}

目的:清空所有有效节点(保留头结点),释放内存并恢复为空表状态。
实现步骤:
若空表直接返回。
p = first->next 指向当前要删除的第一个有效节点。
在循环中:
如果 p 是尾节点:把 last = first 并 last->next = NULL(即变空)。
否则把 p->next->prio = first 并 first->next = p->next(把头结点指向下一个要删除节点)。
free(p),然后更新 p = first->next(下一次循环删除新头之后的节点)。
循环直到 p == NULL(没有有效节点)。
list->size = 0。
时间复杂度:O(n)。
销毁
cpp
void destroy(List* list)
{
clear(list);
free(list->first);
list->first = list->last = NULL;
}
目的:彻底销毁链表,释放头结点以及所有有效节点,最终把 first/last 置 NULL。
实现步骤:
调用 clear(list) 释放所有有效节点并把 size 置 0。
free(list->first):释放头结点内存。
list->first = list->last = NULL:避免野指针。
时间复杂度:O(n)(clear 的成本)。