单向链表(Singly Linked List)
1.核心概念
单向链表是线性表的一种链式存储结构,它解决了顺序存储(如数组)的某些缺点。
2.与顺序存储的对比
|-------|-------------|------------------|
| 对比维度 | 顺序存储(数组) | 链式存储(链表) |
| 存储方式 | 连续的存储单元 | 任意的存储单元(可连续可不连续) |
| 内存占用 | 只需存储数据元素 | 数据元素 + 后继地址指针 |
| 插入/删除 | O(n),需要移动元素 | O(1),只需修改指针 |
| 动态扩展 | 困难,需重新分配 | 容易,动态申请节点 |
| 随机访问 | O(1),直接索引 | O(n),需要遍历 |
| 空间效率 | 无额外指针开销 | 有指针域的开销 |
3.链表节点结构
/**
* 节点(Node) = 数据域(Data Field) + 指针域(Pointer Field)
*
* 数据域:存储元素本身的信息
* 指针域:存储直接后继位置的地址
*
* 两者共同组成数据元素的存储映像
*/
typedef struct node {
DATATYPE data; // 数据域
struct node *next; // 指针域(指向下一个节点)
} LinkNode;
4.链表管理结构
/**
* 链表整体管理结构
* head: 指向第一个节点的指针(头指针)
* clen: 当前链表中有效节点的数量
*/
typedef struct list {
LinkNode *head; // 头指针
int clen; // 当前长度
} LinkList;
5.链表物理与逻辑关系
物理存储(内存中的实际情况)
-
内存地址: 0x1000 0x2000 0x3000
-
节点: [张三|0x2000] → [李四|0x3000] → [王五|NULL]
逻辑结构(我们理解的关系)
张三 → 李四 → 王五
6.基本操作分析
头插法 InsertHeadLinkList
-
时间复杂度:O(1)
-
操作:新节点成为链表的第一个节点
-
特点:插入速度快,但顺序与插入顺序相反
尾插法 InsertTailLinkList
-
时间复杂度:O(n)
-
操作:新节点成为链表的最后一个节点
-
特点:需要遍历到链表尾部,但保持了插入顺序
查找操作
// 按姓名查找(固定条件)
DATATYPE *FindLinkList(LinkList *list, char *name);
// 通用条件查找(解耦合设计)
typedef int (*PFUN)(DATATYPE*, void* arg);
DATATYPE *FindLinkList2(LinkList *list, PFUN fun, void* arg);
设计思想:FindLinkList2 通过函数指针参数,允许调用者自定义查找条件,提高了函数的通用性和灵活性。
删除操作
int DeleteLinkList(LinkList *list, char *name);
关键技巧:需要维护 prev 指针停留在待删除节点的前一个节点,以便正确连接前后节点。
销毁操作
int DestroyLinkList(LinkList *list);
正确顺序:先逐个释放所有数据节点,再释放链表管理结构。
7.链表的核心优势
动态内存管理
// 需要时创建节点
LinkNode *new_node = (LinkNode*)malloc(sizeof(LinkNode));
//不再需要时释放
free(node);
高效的插入删除
-
插入:修改 1-2 个指针
-
删除:修改 1 个指针 + 释放内存
无需移动大量数据
理论上的无限扩展
只要内存足够,链表可以无限增长
不受预先分配大小的限制
8.使用注意事项
边界条件处理
-
空链表操作
-
头节点操作
-
尾节点操作
内存管理
-
及时释放不再使用的节点
-
避免内存泄漏
-
防止野指针
遍历安全
-
检查 next 是否为 NULL
-
避免无限循环