一、什么是顺序表?
顺序表是线性表的顺序存储结构,底层用 一段连续的内存空间(数组) 存放数据,逻辑相邻的元素在物理内存上也相邻。
可以把它理解成:自带长度管理、支持自动扩容的增强版数组。
顺序表分类
- 静态顺序表:固定大小数组,容量写死,不够用就溢出,不实用。
- 动态顺序表:用 malloc/realloc 动态申请空间,容量不够自动扩容,最常用。
二、顺序表结构体设计
我们用结构体封装三个核心信息:
- 数据指针:指向动态数组
- size:当前有效元素个数
- capacity:当前最大容量
// 顺序表存储的数据类型(可改为char/double/结构体)
typedef int SLDataType;
// 动态顺序表结构体
typedef struct SeqList
{
SLDataType* a; // 指向动态数组
int size; // 有效数据个数
int capacity; // 容量(最多能存多少)
} SL;
三、基础操作函数声明
// 初始化
void SLInit(SL* ps);
// 销毁
void SLDestroy(SL* ps);
// 打印
void SLPrint(SL* ps);
// 扩容检查
void SLCheckCapacity(SL* ps);
// 尾插/尾删
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
// 头插/头删
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
// 指定位置插入/删除
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
// 查找
int SLFind(SL* ps, SLDataType x);
四、函数实现
1,初始化
void SLInit(SL* ps)
{
assert(ps);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
2.销毁
void SLDestroy(SL* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
3.打印
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\\n");
}
4.扩容检查
容量满了自动扩容,一般扩为原来 2 倍(有其数学依据):
void SLCheckCapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
// 初始容量给4,否则*2
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity = newCapacity;
}
}
5. 尾插 / 尾删
// 尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->a[ps->size++] = x;
}
// 尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size > 0);
ps->size–;
}
6.头插/头删
// 头插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
// 数据往后挪一位
for (int i = ps->size; i > 0; i–)
{
ps->a[i] = ps->a[i – 1];
}
ps->a[0] = x;
ps->size++;
}
// 头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
for (int i = 0; i < ps->size – 1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size–;
}
7.指定位置插入/删除
// pos下标处插入x
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size; i > pos; i–)
{
ps->a[i] = ps->a[i – 1];
}
ps->a[pos] = x;
ps->size++;
}
// 删除pos下标元素
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size – 1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size–;
}
8.查找
// 找到返回下标,没找到返回-1
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
return i;
}
return -1;
}
五、时间复杂度总结
| 随机访问 | O(1) |
| 尾插 / 尾删 | O(1) |
| 头插 / 头删 | O(n) |
| 指定位置插入 / 删除 | O(n) |
| 查找 | O(n) |
六、顺序表优缺点
优点
- 下标随机访问,查找极快
- 内存连续,CPU 缓存命中率高
- 实现简单,适合大量查询、少量插入删除的场景
缺点
- 头部 / 中间插入删除要挪动数据,效率低
- 扩容有开销,可能存在空间浪费
- 必须连续内存,大片空间不足时容易失败
七、总结
顺序表是数据结构入门第一站,在以后会用到:
- 内存连续存储
- 动态内存管理
- 增删改查的基本思想
- 为后续链表、栈、队列打下基础




