目录
一、定义一个结构体
//顺序表的动态存储
typedef struct SeqList
{
SLDataType* a; //指向之后动态开辟的数组
int size; //存储的有效数据的个数
int capacity; //已开辟空间的容量大小
}SeqList;
二、初始化顺序表
//初始化
void SeqListInit(SeqList* psl)
{
assert(psl);
psl->a = NULL;
psl->size = 0;
psl->capacity = 0;
}
三、销毁动态开辟的空间
//动态开辟的空间使用完要销毁
void SeqListDestroy(SeqList* psl)
{
assert(psl);
free(psl->a);
psl->a = NULL;
psl->size = psl->capacity = 0;
}
四、检查容量并且扩容
因为需要增加顺序表存储的内容,就需要先判断已有数据体的容量是否能保证下一个结构体数据的存储,所以就先要判断容量,如果不够就需要增加容量,保证数据的顺利存储。
新开辟的顺序表的容量可以根据实际情况进行调整,若开辟的较小就需要多次频繁扩容,就造成了效率的降低;若开辟的较大又容易造成空间的浪费;这里是每次都扩大到之前的二倍。
//检查容量,扩容
void SeqListCheckCapacity(SeqList* psl)
{
assert(psl);
//先判断容量是否满了,如果满了就扩容
if (psl->capacity == psl->size)
{
//因为可能是第一次进来扩容,所以需要应该判断开始容量是否为0
size_t newCapacity = psl->capacity == 0 ? 4 : 2 * psl->capacity;
SLDataType* tmp = realloc(psl->a, sizeof(SeqList) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
else
{
psl->a = tmp;
psl->capacity = newCapacity;
}
}
}
五、打印顺序表
这里直接根据顺序表结构的特点依次根据地址找到对应结构体中存储的值打印
void SeqListPrint(SeqList* psl)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
六、尾插
插入数据前都要先检查容量并且不够的话还需要扩容,这里直接调用之前定义的函数。尾插就需要知道顺序表的尾在哪儿,因为顺序表在内存中是一段空间,所以可以类比对数组的理解,顺序表的size位置就是尾之后的一个位置,将需要插入的数据放入size位置就可以,插入一个数据之后再将size++
void SeqListPushBack(SeqList* psl, SLDataType x)
{
assert(psl);
SeqListCheckCapacity(psl);
psl->a[psl->size] = x;
psl->size++;
}
七、尾删
尾删类似于尾插,都需要知道尾的位置。这里不需要将尾所在的空间free()释放掉,直接让size--就相当于把尾位置的数据给删除了,因为之后就不会再访问到那个的位置,之后插入新的数据到该位置时就将原数据覆盖了。
void SeqListPopBack(SeqList* psl)
{
assert(psl);
psl->a[psl->size] = 0;
//注意:要确保psl->size大于0,否则减减之后,再之后使用会发生非法访问
if (psl->size > 0)
{
psl->size--;
}
}
八、头插
基于顺序表的结构以及内存结构,不能直接在顺序表的头直接开辟空间插入新的数据。这里还是需要知道尾的位置,同上,从尾开始依次将数据先后移动,给头部让出一个空间来插入新的数据
void SeqListPushFront(SeqList* psl, SLDataType x)
{
assert(psl);
SeqListCheckCapacity(psl);
int end = psl->size - 1;
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
end--;
}
psl->a[0] = x;
psl->size++;
}
九、头删
头删也是类似于尾删,不用释放掉头部对应的空间,只需要从头的下一个位置开始,依次覆盖掉前面的数据
void SeqListPopFront(SeqList* psl)
{
assert(psl);
int begin = 1;
if (psl->size > 0)
{
while (begin < psl->size)
{
psl->a[begin - 1] = psl->a[begin];
begin++;
}
psl->size--;
}
}
十、在指定位置插入一个数
这里就类似于头插,先从尾部依次将数据向后移一个位置,将指定位置移完之后,再将所要插入的数据放入该位置
//在指定位置插入一个数
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
{
assert(psl);
SeqListCheckCapacity(psl);
//暴力检查
//assert(pos <= psl->size)
//温和检查
if (pos > psl->size)
{
printf("pos 越界:%d", pos);
return;
//exit(-1);
}
//int end = psl->size - 1;
//while (end >= (int)pos) //注意:不同类型的数据作比较会发生整型提升
//{
// psl->a[end + 1] = psl->a[end];
// end--;
//}
size_t end = (size_t)psl->size;
while (end > pos)
{
psl->a[end] = psl->a[end - 1];
end--;
}
psl->a[pos] = x;
psl->size++;
}
十一、删除指定位置处的数
这里也是类似头删,从指定位置的之后一个位置开始,依次将数据向前一个位置覆盖。需要注意指定位置的大小必须大于等于顺序表有效数据的个数,如果小于顺序表有效数据的个数就存在错误,在这就需要对这一前提条件进行断言
void SeqListErase(SeqList* psl, size_t pos)
{
assert(psl);
assert(pos < psl->size);
size_t begin = pos;
while (begin < psl->size)
{
psl->a[begin] = psl->a[begin + 1];
begin++;
}
psl->size--;
}
十二、数据的查找
直接遍历查找
int SeqListFind(SeqList* psl, SLDataType x)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
if (psl->a[i] == x)
return i;
}
return -1;
}
十三、调试
#include"SeqList.h"
void SeqListTest1()
{
SeqList s;
SeqListInit(&s); //初始化
//尾插
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPrint(&s);
//尾删
SeqListPopBack(&s);
SeqListPrint(&s);
//尾插
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPushBack(&s, 6);
SeqListPrint(&s);
//头插
SeqListPushFront(&s, 0);
SeqListPrint(&s);
//头删
SeqListPopFront(&s);
SeqListPrint(&s);
//在指定位置插入一个数
SeqListInsert(&s, 2, 3);
SeqListPrint(&s);
//删除指定位置的数
SeqListErase(&s, 2);
SeqListPrint(&s);
//数据的查找
int a = SeqListFind(&s, 6);
printf("%d\n", a);
}
int main()
{
SeqListTest1();
return 0;
}
结果
通过上面的顺序表基础功能的实现可以发现一个明显的缺点,就是在头插和头删需要遍历顺序表,时间复杂度较大