【数据结构知识分享】顺序表详解
一、存储结构
-
物理相邻性:
若元素a
和b
逻辑相邻,则它们在内存中的地址也连续(如&a[i+1] = &a[i] + sizeof(ElemType)
)。 -
内存布局x:
基地址 + 索引 × 元素大小
,通过首地址直接计算任意位置地址。
二、实现方式
类型 | 实现方式 | 特点 |
---|---|---|
静态分配 | 使用定长数组 | 容量固定,编译时确定大小(int data[100]; ) |
动态分配 | 指针 + malloc /realloc | 运行时可扩容(需手动管理内存) |
// 动态顺序表示例(C语言)
typedef struct {int *data; // 动态数组指针int length; // 当前长度int capacity; // 总容量
} SeqList;// 初始化
void InitSeqList(SeqList *L, int size) {L->data = (int*)malloc(size * sizeof(int));L->length = 0;L->capacity = size;
}
三、核心特点
-
随机访问
-
通过下标直接访问元素,时间复杂度 O(1)
-
计算地址:
Loc(a_i) = base_address + i × sizeof(ElemType)
-
-
存储密度高
-
仅存储元素本身,无额外指针开销(对比链表)
-
-
容量拓展不便
-
静态分配:无法扩容,溢出导致崩溃
-
动态分配:
realloc
扩容需复制全部元素,时间复杂度 O(n)
-
-
插入/删除效率低
-
在位置
i
插入需后移所有后续元素(平均移动n/2
次) -
删除操作需前移元素(平均移动
(n-1)/2
次) -
时间复杂度:O(n)
-
四、操作复杂度分析
操作 | 时间复杂度 | 说明 |
---|---|---|
按索引访问 | O(1) | 直接计算地址 |
头部插入/删除 | O(n) | 需移动所有元素 |
尾部插入/删除 | O(1) | 无需移动元素(空间充足时) |
指定位置插入删除 | O(n) | 平均移动半数元素 |
扩容(动态) | O(n) | 复制旧数据到新空间 |
五、适用场景
-
读多写少:高频随机访问(如二分查找)
-
元素数量稳定:避免频繁扩容
-
注重存储效率:对内存占用敏感的场景
六、代码示例(插入操作)
// 在顺序表位置 i 插入元素 e
bool Insert(SeqList *L, int i, int e) {if (i < 1 || i > L->length + 1) // 校验位置合法性return false;if (L->length >= L->capacity) { // 动态扩容int new_cap = L->capacity * 2;int *new_data = (int*)realloc(L->data, new_cap * sizeof(int));if (!new_data) return false; // 扩容失败L->data = new_data;L->capacity = new_cap;}for (int j = L->length; j >= i; j--) // 后移元素L->data[j] = L->data[j-1];L->data[i-1] = e;L->length++;return true;
}
七、经典问题
-
逆置顺序表
双指针法(头尾交换),时间复杂度 O(n) -
合并有序表
归并思想(需额外空间),时间复杂度 O(m+n) -
删除重复值
-
快慢指针法,时间复杂度 O(n)
-
八、顺序表 vs 链表
特性 | 顺序表 | 链表 |
---|---|---|
访问方式 | 随机访问 | 顺序访问 |
插入/删除效率 | O(n) | O(1)(已知位置) |
存储开销 | 仅数据 | 数据 + 指针 |
内存连续性 | 连续 | 碎片化 |
九、总结
-
优势:随机访问极快、存储紧凑
-
劣势:动态扩容成本高、插入删除效率低
-
设计启示:
-
优先选择顺序表:需高频访问元素,元素数量可预估
-
选择链表:需频繁插入删除,数据规模变化大
-
下一期预告:顺序表的基本操作的实现