一、认识单循环链表
单循环链表是带头结点的单链表 + 闭环设计,尾节点不再指向 NULL,而是回头指向头结点,形成一个首尾相连的环。
这种结构最大的优势:可以从任意节点遍历全表,不再需要判断 NULL,逻辑更统一。
1. 节点结构体设计思路
循环链表节点结构与单链表完全一致,只改变指针指向规则,不改变节点本身。
cpp
struct CNode {
int data; // 数据域:存储有效信息
CNode* next; // 指针域:指向下一个节点
};
2. 单循环链表 vs 普通单链表
- 尾节点指向:单链表→NULL;循环链表→头结点
- 判空条件:单链表 head->next==NULL;循环链表 head->next==head
- 遍历结束:单链表 p==NULL;循环链表 p==head

【单链表 结构示意图】

【单循环链表 结构示意图】
二、基础工具函数
基础函数是插入、删除的前提,包含:初始化、判空、查找、获取长度、打印。
1. 初始化
思路:空表时,头结点指向自己,形成最小闭环。

cpp
// 初始化带头结点的单循环链表
void Init_CircleList(CNode* plist) {
if (plist == nullptr) return;
plist->next = plist; // 空表:头结点指向自身
}
2. 判空
cpp
bool Is_Empty(CNode* plist) {
return plist->next == plist;
}
3. 查找
cpp
CNode* Search(CNode* plist, int val) {
if (plist == nullptr) return nullptr;
CNode* p = plist->next;
// 遍历一圈:回到头结点结束
while (p != plist) {
if (p->data == val) return p;
p = p->next;
}
return nullptr;
}
4. 获取有效长度
cpp
int Get_Length(CNode* plist) {
int cnt = 0;
CNode* p = plist->next;
while (p != plist) {
cnt++;
p = p->next;
}
return cnt;
}
5. 打印链表
cpp
void Show(CNode* plist) {
if (Is_Empty(plist)) {
cout << "链表为空!" << endl;
return;
}
CNode* p = plist->next;
while (p != plist) {
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
三、插入操作
按位置插入: 头插、尾插都是它的特殊情况
- 头插 → pos = 0
- 尾插 → pos = 链表长度
- 中间插 → 0 < pos < 长度
插入思路
-
判断位置合法性
-
找到前驱节点
-
新建节点
-
先连后面,再连前面(防止指针断裂)

【头插示意图】

【尾插示意图】
cpp
// 按位置插入(统一头插、尾插、中间插)
bool Insert_Pos(CNode* plist, int val, int pos) {
int len = Get_Length(plist);
if (pos < 0 || pos > len) return false;
// 新建节点
CNode* newNode = new CNode;
newNode->data = val;
// 找前驱
CNode* p = plist;
for (int i = 0; i < pos; i++) {
p = p->next;
}
// 插入(核心步骤)
newNode->next = p->next;
p->next = newNode;
return true;
}
四、删除操作
删除操作统一为两个核心函数,所有删除场景都能覆盖:
1. 按位置删除
- 头删 → pos = 0
- 尾删 → pos = len - 1
- 中间删 → 通用

【头删示意图】

【尾删示意图】
cpp
// 按位置删除
bool Del_Pos(CNode* plist, int pos) {
if (Is_Empty(plist)) return false;
int len = Get_Length(plist);
if (pos < 0 || pos >= len) return false;
// 找前驱
CNode* p = plist;
for (int i = 0; i < pos; i++) {
p = p->next;
}
// 待删除节点
CNode* q = p->next;
// 跨越指向
p->next = q->next;
// 释放内存
delete q;
return true;
}
2. 按值删除
cpp
// 按值删除
bool Del_Val(CNode* plist, int val) {
if (Is_Empty(plist)) return false;
// 第一步:找目标节点
CNode* p = plist->next;
while (p != plist) {
if (p->data == val) break;
p = p->next;
}
if (p == plist) return false; // 没找到
// 第二步:找前驱
CNode* q = plist;
while (q->next != p) {
q = q->next;
}
// 第三步:删除
q->next = p->next;
delete p;
return true;
}
五、清空与销毁
核心区别
• 清空 :删除所有数据节点,保留头结点,链表还能继续用
• 销毁 :删除所有节点(含头结点),链表彻底销毁
销毁函数
cpp
// 销毁整个循环链表
void Destroy(CNode*& plist) {
CNode* p = plist->next;
CNode* q = nullptr;
// 释放所有数据节点
while (p != plist) {
q = p->next;
delete p;
p = q;
}
// 最后释放头结点
delete plist;
plist = nullptr;
}
六、总结
-
闭环:尾节点指向头结点,无 NULL 指针。
-
判空 :head->next == head;遍历结束:p == head。
-
插入/删除统一:只需要按位置操作,头插尾插、头删尾删都是特例。
-
指针修改原则:先连后面,再连前面,避免环断裂。
-
内存安全:new 必须配套 delete,销毁时必须释放所有节点。
-
应用场景:环形队列、循环播放列表、轮询调度等需要循环遍历的场景。