顺序表是用一块 连续的存储单元 连续存储数据
一、顺序表的结构
数组是一块 连续的空间,因此只需在数组上 存储连续的数据即可
注意: 顺序表中 不允许任意两个相邻的数据之间不存储数据
1. 静态顺序表
若只创建一个数组 data,存储数据时,需要保证数据的存储是连续的,此时便无法确定数据应该存储在数组 data 的什么位置,因此这里需要增加一个变量 size,用来 记录当前存储了多少数据
静态顺序表的结构如下:
#define N 100
//存储数据的类型
typedef int SLDataType;
//静态顺序表
typedef struct SeqList
{
SLDataType data[N];
int size; //存储的数据个数
}SL;
2. 动态顺序表
在静态顺序表中,在运行之前需要指定数组的大小,当存储的数据较少时,会造成空间的浪费,当需要存储 N + 1 个数据时,便无法存储,带来了许多不便,为了满足按需申请连续空间的需求,可以采用动态顺序表
用 malloc,realloc等动态开辟内存的函数,便可以实现按需申请一块连续的空间,并用 data指针来管理,size 变量记录当前存储了多少数据,开始时容量开小一点,当容量不够时,进行扩容即可,所以这里还需要一个变量 capacity 用来记录容量
动态顺序表的结构如下:
//存储数据的类型
typedef int SLDataType;
//动态顺序表
typedef struct SeqList
{
SLDataType* data;
int size; //存储的数据个数
int capacity; //当前容量
}SL;
二、顺序表的函数接口
本篇介绍动态顺序表的函数接口,静态顺序表插入删除的函数接口与其类似,读者掌握了动态顺序表,静态顺序表也就容易了
1. 初始化及销毁
创建一个顺序表之后,顺序表结构中的成员变量存储的都是一些随机值,所以需要对其进行初始化,这里采用 初始化时不分配空间的方式,也可以在初始化时就为其分配一些空间
初始化函数如下:
void SLInit(SL* ps)
{
//ps 不能为空指针
assert(ps);
ps->data = NULL;
ps->size = 0;
ps->capacity = 0;
}
在动态顺序表中:存储数据的空间是由自己开辟的,当不使用时应将其释放
销毁函数如下:
void SLDestroy(SL* ps)
{
//ps 不能为空指针
assert(ps);
//顺序表中存储数据的空间是由 ps->data 指向的
if (ps->data)
{
free(ps->data);
ps->data = NULL;
ps->size = ps->capacity = 0;
}
}
2. 检测容量及打印顺序表
当插入数据时,无论是没有开辟空间还是数据已经放满了,都需要进行扩容,此时顺序表的 size 等于 capacity
检测容量函数如下:
void CheckCapacity(SL* ps)
{
//ps 不能为空指针
assert(ps);
//未开辟空间时需要扩容
//存储容量满时需要扩容
if (ps->size == ps->capacity)
{
//未开辟空间时,这里以开辟 4 个数据为例,读者可以自行定义一个宏来指定初始值
//数据放满时,这里以扩二倍的方式来增长空间,读者可以自行决定扩多少空间
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//realloc 第一个参数为 NULL 时,函数相当于 malloc
SLDataType* tmp = (SLDataType*)realloc(ps->data, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
//开辟空间失败,打印错误信息
perror("realloc");
//结束程序
exit(-1);
}
ps->data = tmp;
ps->capacity = newcapacity;
}
}
为了验证插入、删除等得到的结果是否正确,提供打印顺序表的函数,这里数据类型以 int 为例,当读者采用的类型不同时,自行更改该函数即可
打印顺序表数据的函数如下:
void SLPrint(SL* ps)
{
int i;
//ps 不能为空指针
assert(ps);
//打印数据
for (i = 0; i < ps->size; ++i)
{
printf("%d ", ps->data[i]);
}
printf("\n");
}
3. 尾插尾删
尾插:在顺序表的最后一个位置之后插入数据
尾插函数如下:
void SLPushBack(SL* ps, SLDataType x)
{
//ps 不能为空指针
assert(ps);
//当容量满时,需要扩容
CheckCapacity(ps);
ps->data[ps->size] = x;
ps->size++;
}
尾删:删除顺序表中最后一个位置的数据
尾删函数如下:
void SLPopBack(SL* ps)
{
//ps 不能为空指针
assert(ps);
//数据为空时,不能继续删除
assert(ps->size > 0);
ps->size--;
}
4. 头插头删
头插:在顺序表的第一个位置插入数据
头插函数如下:
void SLPushFront(SL* ps, SLDataType x)
{
int end = ps->size - 1;
//ps 不能为空指针
assert(ps);
//当容量满时,需要扩容
CheckCapacity(ps);
//移动数据
while (end >= 0)
{
ps->data[end + 1] = ps->data[end];
--end;
}
ps->data[0] = x;
ps->size++;
}
头删:删除顺序表的第一个位置
头删函数如下:
void SLPopFront(SL* ps)
{
//这里采用第二种方式
int begin = 1;
//ps 不能为空指针
assert(ps);
//数据为空时,不能继续删除
assert(ps->size > 0);
//移动数据
while (begin < ps->size)
{
ps->data[begin - 1] = ps->data[begin];
++begin;
}
ps->size--;
}
5. 中间插入和删除
中间插入:在合法的 pos 位置插入数据,与头插类似,不同的是应该将 pos 位置以及之后的数据依次向后移动,空出 pos 的位置,将数据 x 插入到 pos 位置即可
中间插入函数如下:
void SLInsert(SL* ps, int pos, SLDataType x)
{
int end = ps->size - 1;
//ps 不能为空指针
assert(ps);
//pos 应该是合法的位置
assert(pos >= 0);
assert(pos <= ps->size);
//当容量满时,需要扩容
CheckCapacity(ps);
//移动数据
while (end >= pos)
{
ps->data[end + 1] = ps->data[end];
--end;
}
ps->data[pos] = x;
ps->size++;
}
在调用中间插入函数 SLInsert 时
- 如果在顺序表尾部插入数据,便和尾插函数的功能一样
- 如果在顺序表头部插入数据,便和头插函数的功能一样
因此在尾插和头插函数的实现中可以直接调用中间插入函数 SLInsert
尾插和头插函数更改如下:
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
SLInsert(ps, ps->size, x);
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
SLInsert(ps, 0, x);
}
中间删除:在合法的 pos 位置删除数据,与头删类似,不同的是因将 pos 位置之后的数据依次向前移动,覆盖 pos 的位置即可
中间删除函数如下:
void SLErase(SL* ps, int pos)
{
//这里采用第二种方式
int begin = pos + 1;
//ps 不能为空指针
assert(ps);
//pos 应该是合法的位置
assert(pos >= 0);
assert(pos < ps->size);
//移动数据
while (begin < ps->size)
{
ps->data[begin - 1] = ps->data[begin];
++begin;
}
ps->size--;
}
在调用中间删除函数 SLErase 时
- 如果在顺序表尾部删除数据,便和尾删函数的功能一样
- 如果在顺序表头部删除数据,便和头删函数的功能一样
因此在尾删和头删函数的实现中可以直接调用中间删除函数 SLErase
尾删和头删函数更改如下:
//尾删
void SLPopBack(SL* ps)
{
SLErase(ps, ps->size - 1);
}
//头删
void SLPopFront(SL* ps)
{
SLErase(ps, 0);
}
6. 查找
查找:如果数据存在返回数据在顺序表中的下标,不存在则返回非下标,这里返回 -1
查找函数如下:
int SLFind(SL* ps, SLDataType x)
{
int i;
//ps 不能为空指针
assert(ps);
//遍历顺序表
for (i = 0; i < ps->size; ++i)
{
if (ps->data[i] == x)
return i;
}
return -1;
}