408答疑
文章目录
- 三、 线性表的链式表示
- 双循环链表
- 单链表与双链表的比较
- 单链表的特点
- 双链表的特点
- 双链表上基本操作的实现
- 双链表的插入操作
- 双链表的删除操作
- 双链表的代码实操
- 定义结点
- 创建一个结点
- 带头结点的双链表初始化
- 创建双链表
- 打印双链表
- 查找结点
- 插入结点
- 在指定节点后插入新节点
- 在指定节点前插入新节点
- 删除结点
- 反转链表
- 排序链表
- 循环链表
- 循环单链表
- 操作描述
- 操作特点
- 循环双链表
- 链表的分类
- 五、参考资料
- 鲍鱼科技课件
- 26王道考研书
三、 线性表的链式表示
双循环链表
单链表与双链表的比较
单链表的特点
- 单链表结点中只有一个指向其后继的指针,使得单链表只能从前往后依次遍历。
- 访问某个结点的前驱(插入、删除操作时),只能从头开始遍历,访问前驱的时间复杂度为 O ( n ) O(n) O(n)。
- 为了克服单链表的这个缺点,引入了双链表。
双链表的特点
- 双链表结点中有两个指针
prior
和next
,分别指向其直接前驱和直接后继,如下图所示。表头结点的prior
域和尾结点的next
域都是NULL
。
- 双链表在单链表结点中增加了一个指向其前驱的指针
prior
,因此双链表的按值查找和按位查找的操作与单链表的相同。但双链表在插入和删除操作的实现上,与单链表有着较大的不同。这是因为“链”变化时也需要对指针prior
做出修改,其关键是保证在修改的过程中不断链。此外,双链表可以很方便地找到当前结点的前驱,因此,插入、删除操作的时间复杂度仅为 O ( 1 ) O(1) O(1)。
双链表上基本操作的实现
双链表的插入操作
- 在双链表中
p
所指的结点之后插入结点*s
,其指针的变化过程如图所示。
-
插入操作的步骤如下:
- 将
s->next
指向p->next
。 - 将
p->next->prior
指向s
。 - 将
s->prior
指向p
。 - 将
p->next
指向s
。
- 将
-
上述代码的语句顺序不是唯一的,但也不是任意的。步骤 1 必须在步骤 4 之前,否则
*p
的后继结点的指针就会丢失,导致插入失败。
双链表的删除操作
- 删除双链表中结点
*p
的后继结点*q
,其指针的变化过程如图所示。
-
删除操作的步骤如下:
- 将
p->next
指向q->next
。 - 将
q->next->prior
指向p
。 - 释放 q 结点空间
- 将
双链表的代码实操
定义结点
- 定义双链表的节点类型,包括数据域、前驱指针和后继指针。
typedef struct DCListNode {
ElemType data; // 数据域
struct DCListNode *prev; // 前驱指针
struct DCListNode *next; // 后继指针
} DCListNode, *DCLinkList;
创建一个结点
- 创建并初始化一个新的双链表节点。
DCListNode* buyNode(ElemType x) {
DCListNode *s = (DCListNode*)malloc(sizeof(DCListNode));
s->data = x;
return s;
}
带头结点的双链表初始化
- 初始化一个带头结点的双链表。
void initDCList(DCListNode **head) {
*head = (DCListNode*)malloc(sizeof(DCListNode));
(*head)->prev = *head;
(*head)->next = *head;
}
创建双链表
- 根据给定数组创建一个双链表。
void createDCList(DCLinkList DCL, ElemType ar[], int n) {
DCListNode *p = DCL;
for (int i = 0; i < n; ++i) {
DCListNode *s = (DCListNode*)malloc(sizeof(DCListNode));
s->data = ar[i];
s->prev = p->prev;
s->next = p;
p->prev->next = s;
p->prev = s;
}
}
打印双链表
- 从头结点的下一个节点开始,打印链表中的每个节点数据,直到回到头结点。
void printDCList(DCLinkList DCL) {
DCListNode *p = DCL->next;
while (p != DCL) {
printf("%d-->", p->data);
p = p->next;
}
printf("Over.\n");
}
查找结点
- 从头结点开始,遍历链表直到找到目标节点或到达头结点。
DCListNode* findDCNode(DCLinkList DCL, ElemType key) {
DCListNode *p = DCL->next;
while (p != DCL && p->data != key)
p = p->next;
if (p != DCL)
return p; // 找到了节点
return NULL; // 没有找到节点
}
插入结点
在指定节点后插入新节点
- 在双链表中指定节点 pos 之后插入一个新节点 x。
void insertDCNodeBack(DCLinkList DCL, DCListNode *pos, ElemType x) {
DCListNode *s = buyNode(x);
s->prev = pos;
s->next = pos->next;
s->next->prev = s;
s->prev->next = s;
}
在指定节点前插入新节点
- 在双链表中指定节点 pos 之前插入一个新节点 x。
void insertDCNodeFront(DCLinkList DCL, DCListNode *pos, ElemType x) {
DCListNode *s = buyNode(x);
s->prev = pos->prev;
s->next = pos;
s->next->prev = s;
s->prev->next = s;
}
删除结点
- 找到目标节点,调整前后节点的指针并释放目标节点。
void deleteDCNode(DCLinkList DCL, ElemType key) {
DCListNode *p = findDCNode(DCL, key);
if (p == NULL)
return; // 删除失败
p->prev->next = p->next;
p->next->prev = p->prev;
free(p);
}
反转链表
- 逐个摘除链表中的节点并重新插入到链表头部。
void reverseDCList(DCLinkList DCL) {
DCListNode *p = DCL->next;
DCListNode *q = p->next;
p->next = DCL;
DCL->prev = p;
while (q != DCL) {
p = q;
q = q->next;
p->next = DCL->next;
p->prev = DCL;
p->prev->next = p;
p->next->prev = p;
}
}
排序链表
- 逐个摘除链表中的节点并按顺序插入到链表中。
void sortDCList(DCLinkList DCL) {
DCListNode *p = DCL->next;
DCListNode *q = p->next;
p->next = DCL;
DCL->prev = p;
while (q != DCL) {
p = q;
q = q->next;
DCListNode *cur = DCL->next;
while (cur != DCL && p->data > cur->data)
cur = cur->next;
p->next = cur;
p->prev = cur->prev;
p->prev->next = p;
p->next->prev = p;
}
}
循环链表
循环单链表
- 循环单链表的特点是最后一个结点的
next
指针指向头结点,形成一个环。这种结构使得链表可以从任意位置开始遍历,而不必担心到达链表的末尾,如下图所示。
- 特点:
- 表尾结点的
next
域指向头结点。 - 判空条件不是头结点的指针是否为空,而是它是否等于头指针
L
。
- 表尾结点的
操作描述
- 循环单链表的插入、删除算法与单链表的几乎一样,所不同的是,若操作是在表尾进行,则执行的操作不同,以让单链表继续保持循环的性质。这是因为循环单链表是一个“环”,所以在任何位置上的插入和删除操作都是等价的,而无须判断是否是表尾。
操作特点
- 在单链表中只能从表头结点开始往后顺序遍历整个链表,而循环单链表可以从表中的任意一个结点开始遍历整个链表。
- 有时对循环单链表不设头指针而仅设尾指针,以使得操作效率更高。其原因是,若设的是头指针,对在表尾插入元素需要
O
(
n
)
O(n)
O(n) 的时间复杂度,而若设的是尾指针
r
,r->next
即头指针,对在表头或表尾插入元素都只需要 O ( 1 ) O(1) O(1) 的时间复杂度。
循环双链表
- 循环双链表在循环单链表的基础上增加了一个指向前驱的指针
prior
,使得链表可以从任意结点向前或向后遍历,如下图所示。
- 特点:
- 头结点的
prior
指针指向表尾结点。 - 尾结点的
next
指针指向头结点。 - 判空条件是头结点的
prior
和next
域都等于L
。
- 头结点的
链表的分类
- 循环链表可以是单链表或双链表,可以带头结点或不带头结点,也可以是循环的或非循环的(
2
3
2^3
23种可能性)。这三种特性可以组合出八种不同的链表类型,如下图所示。
五、参考资料
鲍鱼科技课件
b站免费王道课后题讲解:
网课全程班: