文章目录
1.说明
由于循环链表尾部并未指向空,而是指向头指针。循环单链表尾结点指针域指向头结点;循环双链表尾结点后继指针指向头结点,头结点前驱指针指向尾节点。故而各类基本操作与之前单链表、双链表基本操作相差不大,主要就是在于对尾结点的处理问题,之前非循环链表尾结点指向空,如果要执行插入、删除等可能需要对后继结点取指针域的操作会导致报错,而循环链表就没有这个问题,因为它尾结点并非指向空。综上,在此处不会给出全部的基本操作,仅给出简单插入和删除作为示例,可与博主之前实现单链表、双链表的文章参照查看,改动主要是对于尾结点的处理,其余操作可参照之前文章。
单链表实现:单链表的实现及基本操作(带头结点)(可直接运行)
双链表实现:双链表的实现及基本操作(可直接运行)
2.循环单链表
2.1 基本操作
2.1.1 结构体定义
typedef struct LNode {
int data; // 数据域
struct LNode *next; // 指针域
} LNode, *LinkList;
2.1.2 初始化
// 初始化
bool InitList(LinkList &L) {
// 申请分配空间
L = (LNode*)malloc(sizeof(LNode));
// 分配空间失败的情况
if(L == NULL) {
return false;
}
// 头结点后继指针指向头结点本身
L->next = L;
return true;
}
2.1.3 判空
// 判断循环单链表是否为空
bool Empty(LinkList L) {
// 判断头结点指针域是否指向头结点本身即可
if(L->next == L) {
return true;
} else {
return false;
}
}
2.1.4 判断是否是表尾结点
// 判断结点p是否是表尾结点
bool isTail(LinkList L, LNode *p) {
// 如果p结点指针域指向头结点,那么就是表尾结点(循环链表特性)
if(p->next == L) {
return true;
} else {
return false;
}
}
2.1.5 结点后插入
// 指定结点后插操作:在p结点之后插入元素e
bool InsertNextNode(LNode *p, int e) {
// p不合理的情况
if(p == NULL) {
return false;
}
LNode *s = (LNode *)malloc(sizeof(LNode));
// 内存分配失败的情况
if(s == NULL) {
return false;
}
// 此处先将要插入结点的指针域指向前一个结点的指针域,即后一个结点的地址
// 之后再令前一个结点的指针域指向要插入结点
// 顺序不可颠倒,否则可能出现找不到之后结点的情况
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
2.1.6 删除指定节点
// 删除指定节点
bool DeleteNode(LNode *p) {
// p不合理的情况
if(p == NULL) {
return false;
}
// 此操作可以理解为将p节点与其后继结点进行数据域交换
// 之后再删除其后继结点,可以视作完成了删除,且避免了寻找前驱结点的遍历操作
// 由于是循环单链表,所以在尾部时无需对最后一个结点进行特殊处理
// 因为循环单链表最后一个结点的后继指针指向头结点
// 所以即使p是最后一个结点也可以处理
LNode *q = p->next;
p->data = p->next->data;
p->next = q->next;
free(q);
return true;
}
2.1.7 输出所有元素
// 输出所有链表元素
void PrintList(LinkList L) {
if(Empty(L)) {
printf("the list is empty");
}
LNode* p = L->next;
while(p != NULL && p != L) {
printf("%d ", p->data);
if(p->next == L) {
break;
}
p = p->next;
}
printf("\n");
}
2.2 完整代码
#include<stdio.h>
#include<stdlib.h>
typedef struct LNode {
int data; // 数据域
struct LNode *next; // 指针域
} LNode, *LinkList;
// 初始化
bool InitList(LinkList &L) {
// 申请分配空间
L = (LNode*)malloc(sizeof(LNode));
// 分配空间失败的情况
if(L == NULL) {
return false;
}
// 头结点后继指针指向头结点本身
L->next = L;
return true;
}
// 判断循环单链表是否为空
bool Empty(LinkList L) {
// 判断头结点指针域是否指向头结点本身即可
if(L->next == L) {
return true;
} else {
return false;
}
}
// 判断结点p是否是表尾结点
bool isTail(LinkList L, LNode *p) {
// 如果p结点指针域指向头结点,那么就是表尾结点(循环链表特性)
if(p->next == L) {
return true;
} else {
return false;
}
}
// 指定结点后插操作:在p结点之后插入元素e
bool InsertNextNode(LNode *p, int e) {
// p不合理的情况
if(p == NULL) {
return false;
}
LNode *s = (LNode *)malloc(sizeof(LNode));
// 内存分配失败的情况
if(s == NULL) {
return false;
}
// 此处先将要插入结点的指针域指向前一个结点的指针域,即后一个结点的地址
// 之后再令前一个结点的指针域指向要插入结点
// 顺序不可颠倒,否则可能出现找不到之后结点的情况
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
// 删除指定节点
bool DeleteNode(LNode *p) {
// p不合理的情况
if(p == NULL) {
return false;
}
// 此操作可以理解为将p节点与其后继结点进行数据域交换
// 之后再删除其后继结点,可以视作完成了删除,且避免了寻找前驱结点的遍历操作
// 由于是循环单链表,所以在尾部时无需对最后一个结点进行特殊处理
// 因为循环单链表最后一个结点的后继指针指向头结点
// 所以即使p是最后一个结点也可以处理
LNode *q = p->next;
p->data = p->next->data;
p->next = q->next;
free(q);
return true;
}
// 输出所有链表元素
void PrintList(LinkList L) {
if(Empty(L)) {
printf("the list is empty");
}
LNode* p = L->next;
while(p != NULL && p != L) {
printf("%d ", p->data);
if(p->next == L) {
break;
}
p = p->next;
}
printf("\n");
}
int main() {
LinkList L;
InitList(L);
PrintList(L);
InsertNextNode(L, 5);
InsertNextNode(L, 3);
InsertNextNode(L, 1);
PrintList(L);
DeleteNode(L->next->next);
PrintList(L);
}
2.3 运行截图

3.循环双链表
3.1 基本操作
3.1.1 结构体定义
typedef struct DNode {
int data; // 数据域
struct DNode *prior, *next; // 指针域
} DNode, *DLinkList;
3.1.2 初始化
// 初始化
bool InitDLinkList(DLinkList &L) {
// 申请分配空间
L = (DNode*)malloc(sizeof(DNode));
// 分配空间失败的情况
if(L == NULL) {
return false;
}
// 头结点前驱指针与后继指针都指向头结点本身
L->prior = L;
L->next = L;
return true;
}
3.1.3 判空
// 判空
bool Empty(DLinkList L) {
// 判断头结点后继指针是否指向头结点本身即可
if(L->next == L) {
return true;
} else {
return false;
}
}
3.1.4 判断是否是表尾结点
// 判断结点p是否是表尾结点
bool isTail(DLinkList L, DNode *p) {
// 如果p结点指针域指向头结点,那么就是表尾结点(循环链表特性)
if(p->next == L) {
return true;
} else {
return false;
}
}
3.1.5 结点后插入
// 双链表插入
// 在p结点后插入s结点
bool InsertNextDNode(DNode *p, DNode *s) {
// 无效输入
if(p == NULL || s == NULL) {
return false;
}
// 先令s后继指针指向后继结点
// 再令后继结点的前驱指针指向s
// 再令s前驱指针指向p
// 最后令前驱结点后继指针指向s
// 顺序注意不要在连接前断开即可
s->next = p->next;
// 由于是循环双链表,所以在尾部时无需对最后一个结点进行特殊处理
// 因为循环双链表最后一个结点的后继指针指向头结点,且头结点前驱指针指向尾节点
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
3.1.6 结点后删除
// 删除p结点的后继结点
bool DeleteNextDNode(DNode *p) {
// 无效输入
if(p == NULL) {
return false;
}
// 找到p的后继结点q
DNode *q = p->next;
// 由于是循环双链表,所以在尾部时无需对最后一个结点进行特殊处理
// 因为循环双链表最后一个结点的后继指针指向头结点,且头结点前驱指针指向尾节点
// 用前驱结点的后继指针指向后继结点
p->next = q->next;
// 用q后继结点的前驱指针指向前驱结点
q->next->prior = p;
// 不要忘记释放空间!!!
free(q);
return true;
}
3.1.7 输出所有元素
// 输出所有链表元素
void PrintList(DLinkList L) {
if(Empty(L)) {
printf("the list is empty");
}
DNode* p = L->next;
while(p != NULL && p != L) {
printf("%d ", p->data);
if(p->next == L) {
break;
}
p = p->next;
}
printf("\n");
}
3.2 完整代码
#include<stdio.h>
#include<stdlib.h>
typedef struct DNode {
int data; // 数据域
struct DNode *prior, *next; // 指针域
} DNode, *DLinkList;
// 初始化
bool InitDLinkList(DLinkList &L) {
// 申请分配空间
L = (DNode*)malloc(sizeof(DNode));
// 分配空间失败的情况
if(L == NULL) {
return false;
}
// 头结点前驱指针与后继指针都指向头结点本身
L->prior = L;
L->next = L;
return true;
}
// 判空
bool Empty(DLinkList L) {
// 判断头结点后继指针是否指向头结点本身即可
if(L->next == L) {
return true;
} else {
return false;
}
}
// 判断结点p是否是表尾结点
bool isTail(DLinkList L, DNode *p) {
// 如果p结点指针域指向头结点,那么就是表尾结点(循环链表特性)
if(p->next == L) {
return true;
} else {
return false;
}
}
// 双链表插入
// 在p结点后插入s结点
bool InsertNextDNode(DNode *p, DNode *s) {
// 无效输入
if(p == NULL || s == NULL) {
return false;
}
// 先令s后继指针指向后继结点
// 再令后继结点的前驱指针指向s
// 再令s前驱指针指向p
// 最后令前驱结点后继指针指向s
// 顺序注意不要在连接前断开即可
s->next = p->next;
// 由于是循环双链表,所以在尾部时无需对最后一个结点进行特殊处理
// 因为循环双链表最后一个结点的后继指针指向头结点,且头结点前驱指针指向尾节点
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
// 删除p结点的后继结点
bool DeleteNextDNode(DNode *p) {
// 无效输入
if(p == NULL) {
return false;
}
// 找到p的后继结点q
DNode *q = p->next;
// 由于是循环双链表,所以在尾部时无需对最后一个结点进行特殊处理
// 因为循环双链表最后一个结点的后继指针指向头结点,且头结点前驱指针指向尾节点
// 用前驱结点的后继指针指向后继结点
p->next = q->next;
// 用q后继结点的前驱指针指向前驱结点
q->next->prior = p;
// 不要忘记释放空间!!!
free(q);
return true;
}
// 输出所有链表元素
void PrintList(DLinkList L) {
if(Empty(L)) {
printf("the list is empty");
}
DNode* p = L->next;
while(p != NULL && p != L) {
printf("%d ", p->data);
if(p->next == L) {
break;
}
p = p->next;
}
printf("\n");
}
int main() {
DLinkList L;
InitDLinkList(L);
PrintList(L);
DNode *s1 = (DNode*)malloc(sizeof(DNode));
s1->data = 5;
s1->next = NULL;
DNode *s2 = (DNode*)malloc(sizeof(DNode));
s2->data = 3;
s2->next = NULL;
InsertNextDNode(L, s1);
InsertNextDNode(L, s2);
PrintList(L);
DeleteNextDNode(L);
PrintList(L);
}
3.3 运行截图


本文详细介绍了循环单链表和循环双链表的实现,包括结构体定义、初始化、判断空、判断表尾结点、结点插入与删除等基本操作。循环链表的特点在于尾结点指向头结点,简化了某些操作,如删除最后一个结点。提供了完整的C语言代码示例,并附带了运行截图。
645

被折叠的 条评论
为什么被折叠?



