顺序表是c语言中的一个初阶的数据结构,相对简单,顺序表底层其实就是数组,物理上一定连续,逻辑上也连续
顺序表结构
typedef struct SeqList
{
SLDataType* arr;
int size;
int capacity;
};
这里的"SLDataType* arr;"是指动态开辟多大的空间,"int size;"指的是当前有效的数据个数,"int capacity;"是记录当前动态的空间大小,此时我们就要运用到我们之前学的模块化函数编程的思想

那我们为什么要把顺序表的结构放在头文件里面?因为头文件是类型的"共享声明载体",顺序表的结构需要被多个源文件调用,所以写在头文件里面就很方便,只要其他原文件包含了头文件就能使用顺序表结构,那现在我们就可以根据这个头文件去创建一个顺序表的第一个节点然后再初始化内容,这里需要一个头文件和两个源文件,一个用于实现顺序表的方法,一个则是用于测试方法是否正确

初始化
//初始化
void SLInit(SL* ps);
//顺序表的初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
将函数的声明写在头文件中,函数的实现写在cs.c文件中,通过指针操作符将顺序表初始化为一个空的顺序表,成员大小和空间大小都为0

顺序表已经初始化为空指针和0
销毁
既然有了顺序表的初始化,就会可以销毁顺序表,同样定义一个函数
void SLDestroy(SL* ps)
//顺序表的销毁
void SLDestroy(SL* ps)
{
if (ps->arr)//等价于if(ps->arr !=NULL)
{
free(ps->arr);
ps->arr = NULL;
}
ps->arr = NULL;
ps->capacity = ps->size = 0;
这里模拟的是创建好的顺序表,指针指向的空间如果不为空,就释放掉这个空间,然后将这个指针指向空指针,再把成员大小和空间大小都置为0,就代表顺序表已经销毁了

剩下就来实现顺序表的增删改查功能

插入
尾插
既然要在顺序表的尾部插入一个节点,就应该先创建一个节点,我们可以使用realloc函数实现
void SLCheckcapacity(SL* ps)
//判断空间是否够用
void SLCheckcapacity(SL* ps)
{
//插入之前看空间是否够用
if (ps->capacity == ps->size)
{
//申请空间
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//申请多大内存
if (tmp == NULL)
{
perror("realloc");
}
else//内存开辟成功
{
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
}
如果空间大小和成员大小都一样,说明空间刚好够用,但不能输入一些新的数据,就要扩大空间,如果空间大小为0,就开辟4个字节,如果不为0,就开辟原来空间的2倍,为什么要开辟2倍,而不是其他的倍数呢?这里面就牵扯到了数学知识,这里暂且不管,然后根据这个旧的空间申请新的空间,暂时让tmp指针接收,接下来就检测空间是否开辟成功,没有就返回错误,成功就将暂时的tmp指针赋值给ps指针,再将新的空间大小赋值给就的空间大小,这样就创建好了,接下来就可以尾插了
void SLPushBack(SL* ps, SLDataType x)
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
SLCheckcapacity(ps);
//ps->arr[ps->size] = x;
//++ps->size;
//ps->arr[ps->size++] = x;
assert(ps);
ps->arr[ps->size++] = x;
}
先检查容量,不够就扩容,先让size加1个空间后,将这个空间作为数组下标放入新的元素,成员大小为1,空间大小为4个字节

我重复写了五个尾插的测试代码,现在打印出来
void SLPrint(SL s)
{
for (int i = 0; i < s.size; i++)
{
printf("%d ", s.arr[i]);
}
printf("\n");
}

前插
在插入之前还是要判断一下空间是否够用,不够就扩展空间
void SLPushFront(SL* ps, SLDataType x)
//前插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckcapacity(ps);
//先让顺序表中整体向后移
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
还是先断言ps是否为空指针,然后判断空间是否够用,我就让顺序表整体向后移动一个空间,然后将元素放进去

删除
头删
void SLPopFront(SL* ps)
//头删
void SLPopFront(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
头删就是将第一个删除后,其他剩下的元素向前移动一个元素,然后成员也少一个

尾删
void SLDestroys(SL* ps)
//尾删
void SLDestroys(SL* ps)
{
assert(ps);
ps->size--;
}
尾删就只需要将成员大小减1就可以了

在指定位置之前插入
void SLInsert(SL* ps, int pos, SLDataType x)
//指定位置之前插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(ps >= 0 && pos <= ps->size);
SLCheckcapacity(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
这里就需要三个参数,一个是指向这个顺序表的指针和前一个元素和当前要插入的元素,首先还是要断言ps指针是否为空指针和pos大小是否在成员大小中,循环找到pos,然后根据pos为下标,插入元素,然后成员大小再加1

说明这里我们传递的参数是找到下标为0的元素,在下标为0的元素之前插入66
删除指定位置元素
void SLErase(SL* ps, int 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->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
先要判断要删除的元素是否在成员中,找到这个元素后将这个元素的后一个元素赋值给当前要删除的元素,然后成员大小再减1

这里的参数是删除下标为0的元素
查找指定元素
int SLFind(SL* ps, SLDataType x)
//查找
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
通过下标去寻找这个元素,然后返回的是这个元素的下标,如果没有找到就返回-1
SLInsert(&sl, sl.size, 99);
SLPrint(sl);
int ret = SLFind(&sl, 99);
if(ret< 0)
{
printf("没找到\n");
}
else
{
printf("找到了:%d\n", ret);
}
SLDestroy(&sl);
先在顺序表的最后插入元素99,然后打印出来看是否成功尾插,用ret接收查找到这个元素的下标,判断下标是否小于0,若不小于0就返回下标
