004链表 - 用指针连接的动态线性结构
链表 - 用指针连接的动态线性结构
揭开链条的秘密:链表入门指南
📰 5W1H 发明者故事
Who(何人)- 发明者是谁?
发明者:艾伦·纽厄尔(Allen Newell)、克利夫·肖(Cliff Shaw)和赫伯特·西蒙(Herbert Simon)
背景:
- 纽厄尔(1927-1992):物理学家出身,后来转向人工智能,RAND公司研究员
- 肖(1922-1991):计算机科学家,与纽厄尔合作开发程序
- 西蒙(1916-2001):诺贝尔经济学奖得主,认知科学和人工智能先驱
当时的处境:1950年代中期,三人在RAND公司和卡内基理工学院合作,试图用计算机模拟人类的问题解决过程。他们需要一个能表示动态变化的符号结构的数据结构。
When(何时)- 什么时候发明的?
时间:1955-1956年
时代背景:
- 计算机内存增长到可以存储数千个"字"(word)
- FORTRAN语言刚诞生,但主要用于数值计算
- 人工智能作为一个领域刚刚被命名(1956年达特茅斯会议)
- IPL(Information Processing Language)语言正在开发中
- 还没有"数据结构"这个术语,计算机科学作为独立学科尚未形成
Where(何地)- 在哪里发明的?
地点:
- 美国加利福尼亚州圣莫尼卡的RAND公司(研究与发展公司)
环境: - 冷战时期的美国,政府资助大量军事和基础研究
- RAND是一个智库,聚集了顶尖的科学家和数学家
- 纽厄尔和西蒙共用一间办公室,经常讨论到深夜
- 计算机是IBM 701,需要通过穿孔卡片输入程序
What(何事)- 发明了什么?
数据结构:链表(Linked List)
核心概念:不像数组那样把所有元素排成一排存在一起,链表让每个元素记住"下一个在哪里"。就像寻宝游戏,每个线索指向下一个线索的位置。
关键突破:
- 指针概念:不直接存数据本身,而是存"去哪找数据"的地址
- 动态增长:不需要预先知道要存多少元素,需要时就分配新节点
- 灵活连接:节点可以插入、删除、重排,不需要移动其他元素
- 表处理语言(List Processing):专门为这种结构设计的IPL语言
Why(何因)- 为什么发明?
要解决的问题:
- 逻辑表达式表示:在定理证明程序(Logic Theorist)中,需要表示长度不定的逻辑表达式
- 动态数据增长:数组必须预先定义大小,但定理证明过程中符号表达式的长度根本无法预测
- 符号操作:不同于数值计算,AI需要操作符号、表达式、树状结构
当时的挑战:
- IBM 701只有4096个字(word)的内存
- 没有高级语言,需要用汇编操作内存地址
- 每次内存分配都需要程序员手动管理
- 没有垃圾回收,内存泄漏是严重问题
动机:纽厄尔在做定理证明时意识到,人类的推理过程涉及大量临时假设和回溯,需要一种能动态增长、灵活连接的存储方式。数组的连续存储方式太过僵硬。
How(何果)- 如何实现?有什么影响?
实现思路:
- 每个节点分成两部分:数据区 + 指针区
- 数据区存储实际内容
- 指针区存储下一个节点的内存地址
- 用NULL(或0)表示链表的结束
- 用一个头指针记住链表的开始位置
技术方案:
内存布局示例:
地址100: [数据A] [地址200] --→
地址200: [数据B] [地址300] --→
地址300: [数据C] [NULL] --→ 结束
头指针 = 地址100
历史影响:
- 链表是动态数据结构的开端,开启了计算机科学的新篇章
- 启发了Lisp语言(1958年),至今仍是AI领域的重要语言
- 成为更复杂结构(树、图、哈希表)的基础组件
- 今天的几乎所有编程语言都支持链表或类似的引用结构
今天的使用:
- 操作系统内核的进程管理
- 内存分配器的空闲块管理
- 文件系统的目录结构
- 高级语言的垃圾回收实现
- 数据库的索引结构(B+树等)
名言:纽厄尔后来说,“我们当时并没有意识到自己在发明一种基础数据结构,我们只是想解决定理证明的问题。”
📝 自然语言需求定义
需求名称:实现单向链表,支持动态插入、删除和遍历
功能需求(用精确的中文描述)
-
创建节点:创建一个新节点,存储指定数据
- 输入:整数数据
- 操作:分配内存,存储数据,next指针置为NULL
- 输出:新节点指针,分配失败返回NULL
-
头插法插入:在链表头部插入新节点
- 输入:链表头指针的指针,整数数据
- 操作:创建新节点,新节点指向原头节点,更新头指针
- 输出:成功返回true
-
尾插法插入:在链表尾部插入新节点
- 输入:链表头指针的指针,整数数据
- 操作:如果链表为空则头插;否则遍历到最后,新节点接在最后
- 输出:成功返回true
-
按值删除:删除第一个值为指定数据的节点
- 输入:链表头指针的指针,目标值
- 操作:找到该节点,调整前驱的next指针,释放该节点内存
- 输出:成功删除返回true,未找到返回false
-
按位置查找:获取第N个节点的数据(从0开始计数)
- 输入:链表头指针,位置索引
- 操作:从头遍历N次
- 输出:成功返回true,数据通过指针返回;越界返回false
-
遍历打印:遍历并打印所有节点数据
- 输入:链表头指针
- 操作:从头开始,依次访问每个节点,打印数据
- 输出:无
-
释放链表:释放整个链表的所有节点内存
- 输入:链表头指针
- 操作:依次释放每个节点,避免内存泄漏
- 输出:无
约束条件
- 使用单向指针(只指向下一个节点)
- 头指针可能为NULL(空链表)
- 所有malloc必须有对应的free
- 删除节点时必须正确处理头节点的情况
- 位置索引从0开始,越界必须返回错误
验收标准(必须可验证)
| 编号 | 测试场景(自然语言描述) | 预期结果 | 验证方式 |
|---|---|---|---|
| 1 | 创建包含单个节点10的链表 | 链表非空,头节点数据为10 | 头插10,检查头指针和数据 |
| 2 | 头插法插入10,20,30 | 链表顺序为30→20→10 | 依次头插,遍历验证顺序 |
| 3 | 尾插法插入10,20,30 | 链表顺序为10→20→30 | 依次尾插,遍历验证顺序 |
| 4 | 删除头节点(值为30) | 新头节点为20,链表大小减1 | 删除后遍历验证 |
| 5 | 删除中间节点(值为20) | 链表变为10→30 | 删除后遍历验证 |
| 6 | 删除不存在的值 | 返回false,链表不变 | 尝试删除100,检查返回值和链表 |
| 7 | 查找第1个节点 | 返回20 | 创建10→20→30,查找索引1 |
| 8 | 查找越界位置 | 返回false | 尝试查找索引10 |
| 9 | 释放链表后无内存泄漏 | valgrind显示无泄漏 | 创建长链表,释放,valgrind检测 |
AI 生成提示
基于以上需求和验收标准,用标准C语言实现单向链表。
要求:
1. 使用标准C99
2. 节点结构体:包含int data和struct Node* next
3. 包含完整错误处理(空指针、malloc失败、越界)
4. 内存安全(所有malloc必须有free)
5. 代码必须有详细注释
6. 在main函数中实现所有9个验收标准的测试用例
7. 测试通过输出 "✓ 测试X通过",失败输出 "✗ 测试X失败"
核心函数:
- create_node(data) - 创建节点
- insert_head(&head, data) - 头插
- insert_tail(&head, data) - 尾插
- delete_by_value(&head, value) - 按值删除
- get_by_index(head, index, &value) - 按位置查找
- traverse(head) - 遍历打印
- free_list(head) - 释放链表
💻 C语言实现文件
对应文件: linked_list.c
编译运行:
gcc -o linked_list_test linked_list.c
./linked_list_test
# 内存泄漏检测
valgrind --leak-check=full ./linked_list_test
核心函数:
create_node(data)- 创建节点insert_head(&head, data)- 头插法insert_tail(&head, data)- 尾插法delete_by_value(&head, value)- 按值删除get_by_index(head, index, &value)- 按位置查找traverse(head)- 遍历打印free_list(head)- 释放链表
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)