顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般分为:1. 静态顺序表,使用定长数组存储元素。2. 动态顺序表,使用动态开辟的数组存储(malloc
)。
// 静态顺序表的实现
// 缺陷:空间固定,有可能造成空间浪费,或者空间不够
#define N 1000
typedef int SLDatatype;
struct SeqList
{
SLDatatype a[N]; // 使用固定长度的数组,空间固定
int size;
};
所以接下来会使用动态开辟空间来完成顺序表的实现.
// 动态顺序表实现
// 优势:相比静态的顺序表,空间分配上更加灵活
typedef int SLDatatype;
typedef struct SeqList
{
SLDatatypd* a; // 使用malloc在堆上开辟的空间,可以发生扩容,空间分配更加灵活
int size; // 顺序表已存储的数据个数
int capacity; // 顺序表的容量
}SL; // 重命名为SL,方便写代码
接下来实现顺序表常见的一些函数接口.
// 初始化顺序表,这里默认顺序表初始化4个标准空间(标准空间即: 一个SLDatatype数据的大小)
// 需要使用指针作为参数,否则传过来的只是拷贝对象,不会改变函数外面的值。
void SLInit(SL* psl)
{
psl->a = (SLDatatype*)malloc(sizeof(SLDatatype)*4); // 默认四个标准大小
if(psl->a == NULL) // 判断开辟空间是否成功
{
perror("malloc fail");
return;
}
psl->capacity = 4; // 初始化容量
psl->size = 0; // 初始化已使用的空间大小
}
// 销毁顺序表,因为顺序表中有malloc出来的空间,所以在使用完顺序表之后,需要释放空间,否则会导致内存泄漏
void SLDestroy(SL* psl)
{
free(psl->a);
psl-> = NULL; // 置空,防止野指针
psl->size = 0;
psl->capacity = 0;
}
// 检查开辟的空间是否已全部使用,如果已全部使用,这按照已有容量的2被扩容。
void SLCheckCapacity(SL* psl)
{
if(psl->size == psl->capacity)
{
SLDatatype* tem = (SLDatatype*)realloc(psl->a, sizeof(SLDatatype) * psl->capacity * 2);
if(tem == NULL)
{
perror("realloc fail");
return;
}
psl->a = tem;
psl->capacity *= 2;
}
}
// 向顺序表的末尾插入数据,需要传入想要插入的数据
void SLPushBack(SL* psl, SLDatatype x)
{
SLCheckCapacity(psl); // 检查空间是否全部使用
psl->a[ps->size++] = x; // 尾插数据
// SLTnsert(psl, psl->size, x); // 也可以复用插入函数
}
// 向顺序表的头部插入数据,需要传入想要插入的数据
void SLPushFront(SL* psl, SLDatatype x)
{
// 挪动数据一定要从后往前挪。从前往后挪,可能会导致数据的覆盖问题(用一个临时变量记录被覆盖的位置的值可以解决)
SLCheckCapacity(psl);
int end = psl->size - 1;
while(end >= 0)
{
psl->a[end + 1] = psl->a[end];
--end;
}
psl->a[0] = x;
psl->size++;
// SLTnsert(psl, 0, x); // 同样可以复用插入函数
}
// 删除顺序表尾部的数据
void SLPopBack(SL* psl)
{
if(psl->size == 0)
{
return;
}
psl->size--; // 要注意临界条件,当size为0时,不能--,保持不变
// SLErase(psl, psl->size - 1); // 可以复用删除逻辑
}
// 删除顺序表头部数据
void SLPopFront(SL* psl)
{
assert(psl->size > 0); // 检查是否还有数据可以删除,这种方法比较暴力,可能会导致程序直接停止,建议使用
// if(psl->size == 0)
// return;
int start = 0;
while(start < psl->size - 1) // 直接将后面的数据向前覆盖就可以。
{
psl->a[start] = psl->a[start + 1];
}
psl->size--;
// SLErase(psl, 0); // 复用删除
}
// 在顺序表指定位置插入,需要指定位置,和想要插入的数据
void SLInsert(SL* psl, int pos, SLDatatype x)
{
// 检查pos的位置是否合法
if(0 <= psl && pos <= psl->size) // assert(0 <= pos && pos <= psl->size)
{
SLCheckCapacity(psl);
int end = psl->size - 1; // 只要是插入都要注意数据的挪动
while(end >= pos)
{
psl->a[end + 1] = psl->[end];
--end;
}
psl->a[pos] = x;
psl->size++;
}
}
// 删除顺序表指定位置,需要指定位置
void SLErase(SL* psl, int pos)
{
assert(0 <= pos && pos < psl->size);
int start = pos + 1;
while(start < psl->size) // 删除之后,注意数据的挪动
{
psl->a[start - 1] = psl->a[start];
++start;
}
psl->size--;
}