1. 引言
在计算机科学中,数据结构是组织和存储数据的方式,它直接影响到程序的效率和性能。链表和顺序表(通常基于数组实现)是两种最基本、最常用的线性数据结构。理解它们的原理、特点和应用场景,是学习算法和进行高效编程的基础。
2. 顺序表 (Sequential List)
2.1 什么是顺序表?
顺序表是一种用一段连续的存储单元依次存储数据元素的线性结构。在大多数编程语言中,数组(Array)是顺序表的典型实现。
2.2 核心特点
- 随机访问 (Random Access):通过下标(索引)可以在 O(1) 时间复杂度内访问任意位置的元素。
- 存储密度高:只存储数据元素本身,无需额外的指针开销。
- 内存连续:要求分配一块连续的内存空间。
2.3 基本操作与复杂度
| 操作 | 平均时间复杂度 | 说明 |
|---|---|---|
| 按索引访问 | O(1) | 直接计算内存地址 |
| 尾部插入/删除 | O(1) | 无需移动元素 |
| 头部/中部插入/删除 | O(n) | 需要移动后续所有元素 |
| 查找元素 | O(n) | 需要遍历 |
2.4 代码示例(C语言)
#define MAXSIZE 100 // 顺序表最大容量 typedef struct { int data[MAXSIZE]; // 存储数据元素的数组 int length; // 当前长度 } SeqList; // 初始化顺序表 void InitList(SeqList *L) { L->length = 0; } // 在位置i插入元素e int ListInsert(SeqList *L, int i, int e) { if (L->length >= MAXSIZE) return 0; // 表满 if (i < 1 || i > L->length + 1) return 0; // 位置不合法 for (int j = L->length; j >= i; j--) { L->data[j] = L->data[j-1]; // 将第i个及之后的元素后移 } L->data[i-1] = e; // 插入新元素 L->length++; return 1; }3. 链表 (Linked List)
3.1 什么是链表?
链表是一种通过指针(或引用)将一组零散的内存块串联起来的线性数据结构。每个内存块称为一个“结点”(Node),结点中除了存储数据,还存储了指向下一个结点地址的指针。
3.2 核心特点
- 非连续存储:结点可以分散在内存的任何地方。
- 动态大小:无需预先指定大小,可以方便地插入和删除结点。
- 顺序访问:必须从头结点开始,沿着指针链依次访问,不支持随机访问。
3.3 基本操作与复杂度
| 操作 | 平均时间复杂度 | 说明 |
|---|---|---|
| 按索引访问 | O(n) | 需要从头遍历 |
| 头部插入/删除 | O(1) | 修改头指针即可 |
| 尾部插入/删除 | O(n) / O(1)* | 单链表需遍历,双向循环链表或带尾指针则为O(1) |
| 指定结点后插入/删除 | O(1) | 已知结点指针时 |
3.4 代码示例(C语言)
typedef struct LNode { int data; // 数据域 struct LNode *next; // 指针域,指向下一个结点 } LNode, *LinkList; // 头插法建立单链表 LinkList CreateList_Head(int arr[], int n) { LinkList L = (LinkList)malloc(sizeof(LNode)); // 创建头结点 L->next = NULL; for (int i = 0; i < n; i++) { LNode *p = (LNode*)malloc(sizeof(LNode)); p->data = arr[i]; p->next = L->next; // 新结点指向原第一个结点 L->next = p; // 头结点指向新结点 } return L; } // 在结点p之后插入结点s void InsertAfter(LNode *p, LNode *s) { s->next = p->next; p->next = s; }4. 链表与顺序表的对比
| 对比维度 | 顺序表 | 链表 |
|---|---|---|
| 存储方式 | 连续内存 | 非连续内存(指针链接) |
| 容量 | 静态(需预分配)或动态扩容(成本高) | 动态(按需分配) |
| 访问方式 | 随机访问(O(1)) | 顺序访问(O(n)) |
| 插入/删除效率 | 在头部/中部慢(O(n)) | 在已知位置快(O(1)) |
| 空间开销 | 小(仅数据) | 大(数据+指针) |
| 缓存友好性 | 高(局部性原理) | 低 |
5. 如何选择?
根据具体场景选择合适的数据结构:
- 选择顺序表(数组)当:
- 需要频繁按索引随机访问元素。
- 数据量相对稳定,或可预估最大容量。
- 对内存空间使用效率要求高。
- 需要利用CPU缓存局部性提升性能。
- 选择链表当:
- 需要频繁在任意位置插入或删除元素。
- 数据量变化大,无法预知总大小。
- 内存碎片化严重,难以分配大块连续空间。
- 实现栈、队列、哈希表冲突解决(链地址法)等特定结构。
6. 总结
链表和顺序表各有优劣,没有绝对的“更好”。顺序表胜在访问快、内存紧凑;链表胜在插入删除灵活、大小动态。在实际开发中,许多高级数据结构(如栈、队列、图、哈希表)都是基于它们构建的。理解其底层原理,才能在不同场景下做出最合适的选择。