
🌟《循环链表王国大冒险》🌟
学习目标:
✅ 真正理解循环链表
✅ 会写代码
✅ 会做题
✅ 不再怕"死循环"😄
🏰 第一章:什么是循环链表?
1、🎯 故事:神奇的"永动火车"
(1)普通链表是这样的:
1 → 2 → 3 → NULL
🚫 到头就结束了
(2)但循环链表是:
1 → 2 → 3
↑ ↓
└───────┘
👉 最后一个节点 指回第一个!
2、🧠 核心一句话
👉 循环链表 = 没有 NULL,永远走不完!
⚠️ 第二章:为什么循环链表危险?
1、🎯 故事
你在火车上一直走:
1 → 2 → 3 → 1 → 2 → 3 → 1 → ...
😱 永远停不下来!
2、🚨 错误写法(会死循环)
while(p != NULL) // ❌ 永远不会为 NULL
{
cout << p->data;
p = p->next;
}
3、✅ 正确遍历方法(重点!!!)
👉 方法1:回到起点停止
Node* p = head;
if(head != NULL)
{
do
{
cout << p->data << " ";
p = p->next;
}while(p != head);
}
🏗 第三章:如何创建循环链表?
🎯 方法1:手动连接
Node* a = new Node{1, NULL};
Node* b = new Node{2, NULL};
Node* c = new Node{3, NULL};
a->next = b;
b->next = c;
c->next = a; // 🔥 关键:指回头
Node* head = a;
🎯 方法2:尾插法(推荐🔥)
Node* insert(Node* tail, int x)
{
Node* node = new Node{x, NULL};
if(tail == NULL)
{
node->next = node; // 自己指自己
return node;
}
node->next = tail->next;
tail->next = node;
return node; // 新节点成为尾
}
🧠 关键理解
👉 循环链表常用:
tail->next = head
所以:
👉 只存 tail 就够了!
🔍 第四章:循环链表遍历(重点)
🎯 标准模板(必须会!)
void print(Node* tail)
{
if(tail == NULL) return;
Node* p = tail->next; // head
do
{
cout << p->data << " ";
p = p->next;
}while(p != tail->next);
}
➕ 第五章:插入操作
🎯 1️⃣ 头部插入
Node* insertHead(Node* tail, int x)
{
Node* node = new Node{x, NULL};
if(tail == NULL)
{
node->next = node;
return node;
}
node->next = tail->next;
tail->next = node;
return tail;
}
🎯 2️⃣ 尾部插入(最常用🔥)
Node* insertTail(Node* tail, int x)
{
Node* node = new Node{x, NULL};
if(tail == NULL)
{
node->next = node;
return node;
}
node->next = tail->next;
tail->next = node;
return node; // 更新尾
}
❌ 第六章:删除节点(重点难点🔥)
🎯 删除值为 x 的节点
Node* deleteValue(Node* tail, int x)
{
if(tail == NULL) return NULL;
Node* cur = tail->next; // head
Node* prev = tail;
do
{
if(cur->data == x)
{
// 只有一个节点
if(cur == prev)
{
delete cur;
return NULL;
}
prev->next = cur->next;
// 删除的是尾
if(cur == tail)
tail = prev;
delete cur;
return tail;
}
prev = cur;
cur = cur->next;
}while(cur != tail->next);
return tail;
}
🔄 第七章:循环链表的"反转"难点
👉 ⚠️ 重要:
循环链表一般 不直接反转!
🎯 方法:先变普通链表,再反转
// 1️⃣ 断开
Node* head = tail->next;
tail->next = NULL;
// 2️⃣ 普通反转(你已经会了)
head = reverse(head);
// 3️⃣ 找新尾
Node* p = head;
while(p->next)
p = p->next;
// 4️⃣ 再连成环
p->next = head;
tail = p;
🧠 第八章:循环链表 vs 单链表(核心区别)
| 特点 | 单链表 | 循环链表 |
|---|---|---|
| 结尾 | NULL | 指回头 |
| 遍历 | while | do-while |
| 风险 | 安全 | 死循环⚠️ |
| 常用 | 普通题 | 约瑟夫问题🔥 |
🎯 第九章:经典应用------约瑟夫问题(必考🔥🔥🔥)
1、🎯 故事
(1)一群小朋友围成一圈:
1 2 3 4 5
(2)每数到3的人淘汰!
(3)👉 用循环链表完美解决!
2、💻 核心代码
Node* solve(int n, int k)
{
Node* tail = NULL;
// 建环
for(int i = 1; i <= n; i++)
tail = insertTail(tail, i);
Node* cur = tail->next;
Node* prev = tail;
while(cur != cur->next)
{
for(int i = 1; i < k; i++)
{
prev = cur;
cur = cur->next;
}
prev->next = cur->next;
delete cur;
cur = prev->next;
}
return cur; // 最后一个
}
第九章:🎁 最终口诀(超级重要!)
循环链表要记牢,没有NULL很奇妙
遍历必须do-while,否则程序停不掉
插入删除改指针,tail位置要分清
考试约瑟夫问题常出现,掌握它就轻松过
🏁 学完我们要做到:
✅ 会画循环链表
✅ 会安全遍历(不会死循环)
✅ 会插入 / 删除
✅ 理解 tail 的作用
✅ 会做约瑟夫问题
附:🌟《循环链表5道专项训练(从简单到竞赛)》🌟
🧩 第1题:安全遍历循环链表(基础)
1、🎯 故事
你在"无限火车"上巡逻,不能一直绕圈!
2、🚂 图解
1 → 2 → 3
↑ ↓
└───────┘
3、💡 思路
👉 关键:
✔ 从 head 出发
✔ 回到 head 停止
4、💻 核心代码
void print(Node* tail)
{
if(tail == NULL) return;
Node* p = tail->next; // head
do
{
cout << p->data << " ";
p = p->next;
}while(p != tail->next);
}
🧩 第2题:统计节点个数(进阶一点)
1、🎯 故事
你要数一数"圆形火车"有多少节🚃
2、💡 思路
👉 do-while + 计数器
3、💻核心代码
int count(Node* tail)
{
if(tail == NULL) return 0;
int cnt = 0;
Node* p = tail->next;
do
{
cnt++;
p = p->next;
}while(p != tail->next);
return cnt;
}
🧩 第3题:删除所有值为x的节点(重点🔥)
1、🎯 故事
火车上所有"调皮的乘客x"都要下车!
2、⚠️ 难点
👉 可能删除:
-
头节点
-
尾节点
-
所有节点(变空)
3、💡 思路
👉 用 prev + cur
👉 循环一圈
👉 特别注意 tail 更新!
4、💻核心代码:
Node* deleteAll(Node* tail, int x)
{
if(tail == NULL) return NULL;
Node* cur = tail->next;
Node* prev = tail;
do
{
if(cur->data == x)
{
// 只有一个节点
if(cur == prev)
{
delete cur;
return NULL;
}
prev->next = cur->next;
if(cur == tail)
tail = prev;
Node* tmp = cur;
cur = cur->next;
delete tmp;
}
else
{
prev = cur;
cur = cur->next;
}
}while(cur != tail->next);
return tail;
}
🧩 第4题:循环链表右移k步(技巧🔥)
1、🎯 故事
整个火车向右旋转k步!
2、🚂 示例
原来:1 2 3 4
右移2步 → 3 4 1 2
3、💡 思路
👉 本质:
✔ 找新尾巴
✔ tail往前走
4、💻核心代码
Node* move(Node* tail, int k)
{
if(tail == NULL) return NULL;
int n = 1;
Node* p = tail->next;
while(p != tail)
{
n++;
p = p->next;
}
k %= n;
for(int i = 0; i < k; i++)
tail = tail->next;
return tail;
}
🧩 第5题:约瑟夫问题(竞赛经典🔥🔥🔥)
1、🎯 故事
小朋友围成一圈:
1 2 3 4 5
每数到 m 的人淘汰!
2、💡 思路
👉 循环链表 + 删除
👉 每次走 m-1 步
3、💻核心代码
Node* josephus(int n, int m)
{
Node* tail = NULL;
// 建环
for(int i = 1; i <= n; i++)
{
Node* node = new Node{i, NULL};
if(tail == NULL)
{
node->next = node;
tail = node;
}
else
{
node->next = tail->next;
tail->next = node;
tail = node;
}
}
Node* cur = tail->next;
Node* prev = tail;
while(cur != cur->next)
{
for(int i = 1; i < m; i++)
{
prev = cur;
cur = cur->next;
}
prev->next = cur->next;
delete cur;
cur = prev->next;
}
return cur;
}
🧠 总结:循环链表做题核心
🎯 关键技巧
1️⃣ 一定用 do-while
2️⃣ tail 比 head 更重要
3️⃣ 删除时注意:
- 是否只剩一个节点
- 是否删除tail
4️⃣ 多画图(超级关键)
5️⃣ 环形问题优先想到循环链表