006循环链表 - 首尾相连的环形动态结构
循环链表 - 首尾相连的环形动态结构
循环思考:循环链表入门
📰 5W1H 发明者故事
Who(何人)- 发明者是谁?
发明者:艾伦·纽厄尔(Allen Newell)、克利夫·肖(Cliff Shaw)和赫伯特·西蒙(Herbert Simon)
背景:
- 纽厄尔(1927-1992):人工智能先驱,图灵奖得主(1975年)
- 肖(1922-1991):系统程序员,擅长底层实现
- 西蒙(1916-2001):诺贝尔经济学奖得主,人类认知科学奠基人
当时的处境:在开发IPL(Information Processing Language)语言时,他们发现普通链表无法高效地表示"循环引用"结构,即一组对象互相引用、没有明确起点和终点的场景,例如棋盘上所有合法走步的循环枚举。
When(何时)- 什么时候发明的?
时间:1956年
时代背景:
- IPL-II语言正式发布,这是链表思想的直接载体
- 人工智能的第一次"黄金时代"刚刚开始
- 计算机内存仍以磁鼓和磁芯为主,容量极小
- 程序员需要手工管理每一个字节的内存
- 没有操作系统的内存管理,程序自己分配和回收空间
Where(何地)- 在哪里发明的?
地点:美国加利福尼亚州圣莫尼卡的RAND公司
环境:
- 冷战军备竞赛推动了大量基础计算研究
- RAND公司拥有当时最先进的IBM 701计算机
- 纽厄尔和西蒙的合作跨越了RAND公司和卡内基理工学院
- 研究氛围自由,鼓励跨学科探索
What(何事)- 发明了什么?
数据结构:循环链表(Circular Linked List)
核心概念:在普通链表基础上,将最后一个节点的next指针指回头节点(而非NULL),形成一个"环"。就像钟表的表盘,从12点出发,沿着时针方向走,最终回到12点。
关键突破:
- 无终点遍历:从任意节点出发,都能访问到所有节点
- 头尾等价:头节点和尾节点不再有本质区别,只需要维护一个指针
- 循环问题建模:天然适合"轮询"、"约瑟夫环"等循环场景
- 哨兵节点:Knuth在TAOCP中推广用虚拟头节点简化边界处理
Why(何因)- 为什么发明?
要解决的问题:
- 循环枚举:某些算法需要反复循环遍历一组元素(如进程调度轮转)
- 无终点连接:普通链表的NULL尾部在循环场景中是一个"伪终点",需要特殊处理
- 双向访问:可以从任意节点向前追溯整个序列
当时的挑战:
- 普通链表需要额外的"尾指针"才能高效尾插;循环链表只需维护尾节点即可同时O(1)访问头和尾
- 循环引用导致垃圾回收困难(这也推动了标记-清扫GC的发明)
动机:约瑟夫问题(Josephus Problem)是一个古老的数学题,需要循环遍历一组人并逐一淘汰。普通链表处理这类问题极其繁琐,而循环链表使之变得自然直观。
How(何果)- 如何实现?有什么影响?
实现思路:
- 最后节点的next指向头节点(而非NULL)
- 通常维护tail指针(尾节点),通过tail->next即可得到头节点
- 遍历时以"回到起点"作为终止条件
技术方案:
循环链表示意:
┌─────────────────────────┐
↓ │
[头:A] → [B] → [C] → [尾:D]
tail = D, tail->next = A(头节点)
历史影响:
- 操作系统的进程调度(Round-Robin算法)天然使用循环链表
- Linux内核的
list_head结构本质上是循环双向链表 - 约瑟夫问题成为算法竞赛的经典题目,用循环链表解决优雅简洁
- Knuth在TAOCP第1卷2.2.4节专门讨论循环链表及其与普通链表的对比
今天的使用:
- Linux内核调度器(CFS)的任务队列
- 音频/视频播放器的循环播放列表
- 网络协议的令牌环(Token Ring)
- 数据库连接池的连接复用
名言:Knuth写道,“循环链表的美妙之处在于,你永远不需要判断是否到达链表末尾——因为根本没有末尾。”
📝 自然语言需求定义
需求名称:实现循环链表,支持首尾插入、任意删除、循环遍历和约瑟夫环模拟
功能需求(用精确的中文描述)
-
创建空循环链表:初始化一个空的循环链表
- 输入:无
- 操作:分配哨兵/头节点,将其next指向自身
- 输出:循环链表指针
-
头部插入:在链表头部(第一个有效节点位置)插入新节点
- 输入:链表指针,整数数据
- 操作:创建新节点,插入到头节点之后
- 输出:成功返回true
-
尾部插入:在链表尾部插入新节点
- 输入:链表指针,整数数据
- 操作:找到当前最后一个节点,在其后插入新节点
- 输出:成功返回true
-
删除节点:删除第一个值为target的节点
- 输入:链表指针,目标值
- 操作:遍历找到目标节点,调整前驱指针,释放内存
- 输出:成功返回true,未找到返回false
-
循环遍历:从头节点出发,打印所有节点,回到头节点停止
- 输入:链表指针
- 操作:从第一个有效节点遍历,走完一圈
- 输出:打印所有数据
-
从任意节点出发遍历:给定一个节点指针,沿next方向遍历整圈
- 输入:任意节点指针,头节点指针(用于判断终止)
- 操作:沿next遍历,回到原节点停止
- 输出:打印经过的所有节点数据
-
约瑟夫环:n个人围成一圈,每数到k淘汰一人,返回最后幸存者
- 输入:人数n,间隔k
- 操作:构建循环链表,模拟约瑟夫过程
- 输出:最后幸存者的编号
-
搜索节点:查找第一个值为target的节点
- 输入:链表指针,目标值
- 操作:从头遍历,找到则返回节点指针
- 输出:找到返回节点指针,未找到返回NULL
-
释放链表:释放所有节点内存
- 输入:链表指针
- 操作:遍历所有节点,逐一释放
- 输出:无
约束条件
- 使用虚拟头节点(哨兵节点)简化边界处理
- 链表始终保持循环性(最后节点的next始终指向头节点)
- 空循环链表中,哨兵节点的next指向自身
- 所有malloc必须有对应的free
- 遍历终止条件:回到起点节点(不是NULL)
验收标准(必须可验证)
| 编号 | 测试场景(自然语言描述) | 预期结果 | 验证方式 |
|---|---|---|---|
| 1 | 创建空循环链表 | 哨兵节点的next指向自身,链表有效 | 检查head->next == head |
| 2 | 头插1,2,3后遍历 | 遍历顺序为3,2,1 | 依次头插,验证遍历结果 |
| 3 | 尾插1,2,3后遍历 | 遍历顺序为1,2,3 | 依次尾插,验证遍历结果 |
| 4 | 从任意节点出发遍历完整圈 | 访问到所有节点,不多不少 | 从中间节点出发,计数验证 |
| 5 | 删除头部第一个有效节点 | 链表正确缩短,仍保持循环 | 删除后验证循环性和节点数 |
| 6 | 删除不存在的值 | 返回false,链表不变 | 尝试删除999,检查返回值 |
| 7 | 约瑟夫环n=7,k=3 | 最后幸存者为编号4 | 模拟7人数3淘汰,验证结果 |
| 8 | 搜索存在的值 | 返回正确节点指针,数据匹配 | 插入10,20,30,搜索20 |
| 9 | 搜索不存在的值 | 返回NULL | 在链表中搜索999 |
| 10 | 释放后内存无泄漏 | valgrind无泄漏报告 | 创建100节点链表,释放检测 |
AI 生成提示
基于以上需求和验收标准,用标准C语言实现循环链表。
要求:
1. 使用标准C99
2. 使用虚拟头节点(哨兵节点)设计,head->next是第一个有效节点
3. 包含完整错误处理(空指针、malloc失败)
4. 内存安全(所有malloc必须有free)
5. 代码必须有详细注释
6. 在main函数中实现所有10个验收标准的测试用例
7. 测试通过输出 "✓ 测试X通过",失败输出 "✗ 测试X失败"
核心函数:
- create_circular_list() - 创建空循环链表(含哨兵节点)
- insert_front(list, data) - 头部插入
- insert_back(list, data) - 尾部插入
- delete_node(list, target) - 按值删除
- traverse(list) - 循环遍历打印
- search(list, target) - 搜索节点
- josephus(n, k) - 约瑟夫环问题
- free_circular_list(list) - 释放内存
💻 C语言实现文件
对应文件: circular_linked_list.c
编译运行:
gcc -o circular_linked_list_test circular_linked_list.c
./circular_linked_list_test
# 内存泄漏检测
valgrind --leak-check=full ./circular_linked_list_test
核心函数:
create_circular_list()- 创建空循环链表(哨兵节点)insert_front(list, data)- 头部插入insert_back(list, data)- 尾部插入delete_node(list, target)- 按值删除traverse(list)- 循环遍历search(list, target)- 搜索节点josephus(n, k)- 约瑟夫环模拟free_circular_list(list)- 释放链表
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)