前言:内容包括:顺序表的概念及其结构,动态顺序表的增删查改函数,初始化函数和销毁函数
目录
顺序表的概念及其结构
静态顺序表:
动态顺序表:
动态顺序表的初始化:SLInit
声明:
定义:
动态顺序表的销毁:SLDestroy
声明:
定义:
动态顺序表的打印:SLPrint
声明:
定义:
动态顺序表的增容:SLCheckCapacity
定义:
动态顺序表的增加元素 - 尾插 SLPushBack
声明:
定义:
动态顺序表的增加元素 - 头插 SLPushFront
声明:
定义:
动态顺序表的删除元素 - 尾删 PopBack
声明:
定义:
动态顺序表的删除元素 - 头删 SLPopFront
声明:
定义:
动态顺序表的查找元素:SLFind
声明:
定义:
动态顺序表的修改元素: SLModify
声明:
定义:
动态顺序表的任意插入元素:SLInsert
声明:
定义:
动态顺序表的任意删除元素:SLErase
声明:
定义:
使用定长数组存储元素 ,空间给小了不够用,给大了浪费,不建议使用
使用动态开辟的数组存储,现实中都是使用动态顺序表,根据需要动态地分配空间大小
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;//指向动态开辟的数组
int sz;//有效数据的个数
int capacity;//容量空间的大小
}SL;
为了方便某些情况下修改数组元素的类型,我们将数组元素的类型,类型重定义一下(使用typedef),用SLDataType来代表数组元素的类型,比如之前我们在数组中存储的是int类型的元素,现在我们需要存储double类型的元素,则只需要将int 修改为double
typedef double SLDataType;
void SLInit(SL* psl);
动态的顺序表是一个结构体,结构体的传参需要传结构体的地址 ,用结构体的指针来接收
不要直接传结构体变量本身
void SLInit(SL* psl)
{
assert(psl);//断言 当传参是NULL时报错
psl->a = (SLDataType*)malloc(sizeof(SLDataType) * 4);//初始时动态开辟4个空间
if (psl->a == NULL)
{
perror("malloc");//动态开辟空间失败,打印错误信息
return;
}
psl->sz = 0;
psl->capacity = 4;
}
动态开辟的空间在不使用时需要销毁,归还空间给操作系统
void SLDestroy(SL* psl);
void SLDestroy(SL* psl)
{
assert(psl);
free(psl->a);
psl->a = NULL;
psl->sz = 0;
psl->capacity = 0;
}
void SLPrint(SL* psl);
void SLPrint(SL* psl)
{
assert(psl);
int i = 0;
for (i = 0; i < psl->sz; i++)
{
printf("%d ", psl->a[i]);
}
}
每次在增加元素(插入元素)前需要检查空间的容量,不够就需要增容
void SLCheckCapacity(SL* psl)
{
assert(psl);
if (psl->sz == psl->capacity)
{
SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType) * psl->capacity * 2);
if (tmp == NULL)
{
perror("realloc");
return;
}
psl->a = tmp;
psl->capacity *= 2;
}
}
调整原有空间大小使用realloc函数,一般增容至原来空间的2倍即可
realloc函数调整空间大小:若是原空间后面有足够的空间可以被开辟,则realloc函数会在原空间后追加空间,若是没有足够的空间,则realloc函数会重新找一块足够大的地方,一共开辟2倍的原空间大小,并将原空间里的所有数据拷贝至新空间,销毁原空间,返回新空间的地址
但是realloc是原地开辟还是异地开辟,我们无从知晓,并且realloc开辟可能会失败,返回NULL,因为找不到合适的空间,则我们不能直接用指向原空间的指针去接收realloc的返回值,若是realloc开辟失败,返回NULL,我们又用指向原空间的指针去接收,则我们不仅没有成功扩容,还无法找到原空间的数据了
综上:使用一个指针变量接收realloc的返回值,若是realloc的返回值不为NULL,说明空间开辟成功,再将增容成功后的地址重新赋给指向原空间的指针a
在插入元素之前需要判断容量,不够就增容,使用之前实现的SLCheckCapacity
void SLPushBack(SL* psl, SLDataType x);
void SLPushBack(SL* psl, SLDataType x)
{
assert(psl);
SLCheckCapacity(psl);//检查容量,不够就增容
psl->a[psl->sz++] = x;
}
尾插就是在数组的尾部插入元素,假设现在数组中已有sz个元素,数组最后一个元素的下标是sz-1,则要尾部插入的元素位置的下标就是sz
在插入元素之前需要判断容量,不够就增容,使用之前实现的SLCheckCapacity
void SLPushFront(SL* psl, SLDataType x);
void SLPushFront(SL* psl, SLDataType x)
{
assert(psl);
SLCheckCapacity(psl);
int end = psl->sz - 1;
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
end--;
}
psl->a[0] = x;
psl->sz++;
}
在首元素的位置插入元素,需要将所有的元素后移一位
在删除元素之前需要判断元素个数是否为空,若为空则不用删了
void SLPopBack(SL* psl);
void SLPopBack(SL* psl)
{
assert(psl);
assert(psl->sz > 0);//元素个数为空会报错
psl->sz--;
}
在删除元素之前需要判断元素个数是否为空,若为空则不用删了
void SLPopFront(SL* psl);
void SLPopFront(SL* psl)
{
assert(psl);
assert(psl->sz > 0);
int start = 0;
while (start < psl->sz - 1)
{
psl->a[start] = psl->a[start + 1];
start++;
}
psl->sz--;
}
删除首元素使用覆盖法
int SLFind(SL* psl, SLDataType x);
int SLFind(SL* psl, SLDataType x)
{
assert(psl);
int i = 0;
for (i = 0; i < psl->sz; i++)
{
if (psl->a[i] == x)
{
return i;
}
}
return -1;
}
若能找到被查找的元素,则返回它的下标,若是找不到则返回-1,因为合法的下标不会为负数
void SLModify(SL* psl, int pos,SLDataType x);
void SLModify(SL* psl, int pos, SLDataType x)
{
assert(psl);
assert(pos >= 0 && pos < psl->sz);
psl->a[pos] = x;
}
注意:要查找的位置pos必须是有效范围,即0~sz-1
在插入元素之前需要判断容量,不够就增容,使用之前实现的SLCheckCapacity
void SLInsert(SL* psl, int pos, SLDataType x);
void SLInsert(SL* psl, int pos, SLDataType x)
{
assert(psl);
assert(pos >= 0 && pos <= psl->sz);
SLCheckCapacity(psl);
int end = psl->sz-1;
while (end >= pos)
{
psl->a[end + 1] = psl->a[end];
end--;
}
psl->a[pos] = x;
psl->sz++;
}
注意:插入元素的位置必须合理,可以是0~sz,数组元素的存储必须是连续的
比如现在数组的元素个数是10个,pos不能选定为下标为20的位置,因为在下标为20的位置存储一个元素,则会违背数组元素必须连续存储的原理
SLInsert函数可以用于头插,尾插:
void SLPushBack(SL* psl, SLDataType x)
{
assert(psl);
SLInsert(psl,psl->sz,x);
}
void SLPushFront(SL* psl, SLDataType x)
{
assert(psl);
SLInsert(psl,0,x);
}
因为在SLInsert函数内部自会检查空间容量,不够会增容,所以在SLPushBack和SLPushFront函数内部无需去检查空间容量
在删除元素之前需要判断元素个数是否为空,若为空则不用删了
void SLErase(SL* psl, int pos);
void SLErase(SL* psl, int pos)
{
assert(psl);
assert(pos >= 0 && pos < psl->sz);
int start = pos;
while (start < psl->sz - 1)
{
psl->a[start] = psl->a[start + 1];
start++;
}
psl->sz--;
}
assert(pos >= 0 && pos < psl->sz):已经包含psl->sz>0的条件
则不用再写 assert(psl->sz > 0)
SLErase函数可用于头删,尾删:
void SLPopFront(SL* psl)
{
assert(psl);
SLErase(psl,0);
}
void SLPopBack(SL* psl)
{
assert(psl);
SLErase(psl,psl->sz-1);
}
因为 SLErase函数内部自会检查元素个数是否为0,所以在PopBack和PopFront函数内部无需再去判断元素个数是否为空