这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战
双向链表是线性表的链式存储结构的又一种形式。
双向链表也简称双链表。
循环链表的不足
双向链表的构造
双向链表:链表的每一个链结点中除了数据域以外设置两个指针域,一个指向结点的直接前驱节点,另一个指向结点的直接后继结点。
链结点的构造
双向链表的描述
typedef struct node {
ElemType data;
struct node *llink, *rlink;
} DNode, *DLinkList; // 定义一个双向链表类型
不带头结点的双向链表
不带头结点的双向循环链表
带头结点的双向循环链表
双向循环链表有一个特性,即若p为指向链表中某结点的指针,在表达式中有:
p->llink->rlink = p->rlink->llink = p
双向链表的插入与删除算法
双向链表的一些操作与线性链表的算法基本相同。双向链表与单链表不同的地方是:
- 在双向链表中插入一个新的链结点
- 删除双向链表中一个链结点
在双向链表中插入一个新的链结点
※在带有头结点的双向循环链表中第1个数据域内容为x的结点右边插入一个数据信息为item的新结点。 基本思想
算法
int INSERTD(DLinkList list, ElemType x, ElemType item)
{
DLinkList p, q;
q = list->rlink; // 首先q指向头结点后面的那个结点
while(q != list && q->data != x) // 寻址第1个满足条件的结点
q = q->rlink;
if (q == list) {
ERRORMESSAGE("链表中无满足条件的结点!");
return -1; // 插入失败,返回-1
}
p = (DLinkList)malloc(sizeof(DNode)); // 申请一个新的链结点
p->data = item; // 新结点的值为item
p->llink = q; // 新结点左指针为q
p->rlink = q->rlink; // 新结点的右指针为q的右指针
// 下面两句赋值次序不能颠倒,如果颠倒了,就必须在对其中的一条语句做相应的修改
q->rlink->llink = p; // q的右指针的左指针指向p
q->rlink = p; // q的右指针指向p
return 1; // 插入成功,返回1
}
时间复杂度
- 问题规模:双向链表的长度n
- 查找满足条件的结点(查找插入点位置):O(n)
- 插入一个新结点的过程:O(1)
- 总的时间复杂度:O(n)
删除双向链表中一个链结点
※从带有头结点的双向循环链表中删除第1个数据域内容为x的结点. 思想:
算法:
int DELETED(DLinkList list, ElemType x)
{
DLinkList q;
q = list->rlink; // 首先q指向头结点后面的那个结点
while(q != list && q->data != x)
q = q->rlink;
if (q == list) {
ERRORMESSAGE("链表中无满足条件的结点!");
return -1; // 删除失败,返回-1
}
q->llink->rlink = q->rlink; // ※ q的前驱结点的右指针指向q的右指针
q->rlink->llink = q->llink; // ※ q的后继结点的左指针指向q的左指针
free(q); // 释放结点q
return 1; // 删除成功,返回1
}
时间复杂度: O(n)
若线性表的主要操作是插入和删除,尤其是表的最后那个元素之后插入一个新元素,或者删除表的最后那个元素,采用带头结点的双向循环链表存储结构最节省运算时间。