什么是顺序表?
- 地址连续:所有元素挨在一起存放,中间不留空位置
- 逻辑顺序 = 物理顺序:数据前后顺序,和内存里摆放顺序完全一致
- 随机访问快 :通过下标能立刻找到任意元素(查询效率高)
顺序表分类
顺序表可以分为静态顺序表和动态顺序表
|----------------|-------------------|
| 静态顺序表 | 动态顺序表 |
| 一开始就规定好了数组的空间, | 数组空间由动态申请获得 |
| 适用的场景较少 | 适用场景较多,但是实现起来比较困难 |
动态顺序表的实现
顺序表的结构

cpp
typedef int SqDataType; // 数据元素类型
typedef struct {
SqDataType* arr; // 存储数据的动态数组的指针
int size; // 记录顺序表中已经存入的数据个数
int capacity; // 动态数组的容量空间的大小
}SqList;
初始化顺序表
将结构体的arr,size,capacity进行初始化
对于数组我们要动态申请空间也就是要用到malloc函数

基本用法
cpp
int *p = (int *)malloc(sizeof(int));
实现初始化
cpp
void SqListInit(SqList* ps)
{
SqDataType* space=(SqDataType*)malloc(sizeof(SqDataType) * 4);
if (space == NULL)
{
printf("申请空间失败\n");
return;
}
ps->arr = space;
ps->size = 0;
ps->capacity = 4;
}
销毁顺序表
将size,capacity归零,free数组
cpp
void SqListDestroy(SqList* ps)
{
assert(ps);
ps->size = 0;
free(ps->arr);
ps->arr = NULL;
ps->capacity = 0;
}
插入顺序表
顺序表的插入是需要挪动数据的

如果我们从前往后挪动数就会让数字覆盖其他的数字,我们要采取从后往前挪动,然后在插入


但是我们是动态顺序表我们要考虑到阔容的问题
此时我们就要用到我们的relloc

由于realloc函数阔容存在原地阔容和异地阔容这两种,还有可能阔容失败的可能,我们为了保护数据的安全性我们需要用一个新的变量先保存这个阔容好的地址
cpp
void SqListInsert(SqList* ps, int i, SqDataType x)
{
assert(ps);
assert(i <= ps->size );
assert(i >= 0);
//不需要扩容时
if (ps->size< ps->capacity)
{
++ps->size;
for (int j = ps->size-1; j > i; j--)
{
ps->arr[j] = ps->arr[j - 1];
}
ps->arr[i] = x;
}
//需要阔容时
else
{
int newcapacity = 2 * ps->capacity;
SqDataType* newspace = (SqDataType*)realloc(ps->arr, sizeof(SqDataType) * newcapacity);
if (newspace == NULL)
{
printf("申请空间失败\n");
return;
}
ps->arr = newspace;
ps->capacity = newcapacity;
for (int j = ps->size; j > i; j--)
{
ps->arr[j] = ps->arr[j - 1];
}
ps->arr[i] = x;
++ps->size;
}
}
删除顺序表
这里也涉及到数据的移动,和插入不同的是数据是从前往后挪动,完成数据的覆盖

cpp
SqDataType SqListDelete(SqList* ps, int i)
{
assert(ps && ps->arr);
assert(i < ps->size && i >= 0);
SqDataType del = ps->arr[i];
for (int j = i; j < ps->size; j++)
{
ps->arr[j] = ps->arr[j + 1];
}
--ps->size;
return del;
}
打印顺序表
大音顺序表,就是将表进行遍历一遍,从头走到尾
cpp
void SqListPrint(SqList* ps)
{
assert(ps);
assert(ps->arr);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
}
顺序表的判空,有效元素个数,,头插尾插,头删尾删
这些都比较简单不做过多赘述,头插尾插,头删尾删都可以复用插入和删除
cpp
// 检测顺序表是否为空,空返回true,否则返回false
bool EmptySqList(SqList* ps)
{
assert(ps);
return ps->size == 0;
}
// 获取顺序表中有效元素个数
int SqListSize(SqList* ps)
{
assert(ps);
return ps->size;
}
// 以下接口复用上面的Insert和Delete即可完成
// 头插尾插
void SqListPushBack(SqList* ps, SqDataType x)
{
assert(ps);
SqListInsert(ps, ps->size, x);
}
void SqListPushFront(SqList* ps, SqDataType x)
{
assert(ps);
SqListInsert(ps, 0, x);
}
// 头删尾删
void SqListPopBack(SqList* ps)
{
assert(ps);
SqListDelete(ps, ps->size - 1);
}
void SqListPopFront(SqList* ps)
{
assert(ps);
SqListDelete(ps, 0);
}
小技巧
1. 指针相关断言
- 访问指针本身时,需要对指针断言(如
assert(ps);),防止传入空指针。 - 访问指针指向的成员(如
ps->arr)时,需要对该成员断言(如assert(ps->arr);),防止数组指针为空。
2. 位置合法性断言
在插入、删除操作中,必须对操作位置 i 做断言,确保位置合法:
- 插入位置范围:
0 ≤ i ≤ ps->size(尾插时i == ps->size是允许的)。 - 删除位置范围:
0 ≤ i < ps->size(不能删除不存在的元素)。
3. 核心原则
断言的本质是:代码里要访问什么,就对什么做断言;操作涉及边界,就对边界做断言,以此提前捕获非法操作,避免后续出现内存访问错误。