什么是顺序表
顺序表是用一段物理地址连续的储存单元依次存储数据元素的线性结构,一般情况下采取数组存储。在数组上完成数据的增删查改。
静态顺序表
静态顺序表,采用固定的数组长度,数组容量不可改变,应用场景较少,缺点明显
举个例子
如果数组长度为100,则只能用于小规模数据的场景,如果数据只有3到5个,还会造成空间的浪费
动态顺序表
动态顺序表,用指针管理一片空间,可以根据需要,使用malloc,realloc函数调整空间的大小
实现动态顺序表
创建顺序表
typedef int datetype;//将数组类型重命名,当数组类型改变时方便修改
typedef struct SeqList
{
datetype* arr;//动态开辟的数组
size_t size;//有效数据的个数
size_t capicity;//整个数组可以容纳的数据个数
}SeqList;//重命名顺序表,方便后续修改
这样开辟的顺序表,可以增加代码的可维护性
顺序表功能
- 打印顺序表
- 在指定位置增加数据
- 删除指定位置的数据
- 查找某个数据是否存在
- 对指定数据进行修改
- 自动扩容
- 对顺序表初始化
- 销毁顺序表
具体代码实现
那么我们一个一个来实现这些功能
顺序表初始化
void SLinit(SeqList* s1)//初始化
{
assert(s1);
s1->arr = (SeqList*)malloc(sizeof(SeqList) * init_size);//开辟少量空间
s1->capicity = init_size;//总容量
s1->size = 0;//当前有效数据个数
}
在初始化中使用assert断言,防止空指针传入,同时为arr开辟少量空间
自动扩容
void check_capicity(SeqList* s1)
{
assert(s1);
if (s1->size == s1->capicity)//检查数据个数
{
SeqList* tmp=(SeqList * )realloc(s1->arr, sizeof(SeqList) * s1->capicity * 2);//满了就扩容
if (tmp == NULL)
{
perror("realloc fail");//扩容失败
return;
}
s1->arr = tmp;//更新arr
s1->capicity *= 2;//更新容量大小
}
return;
}
自动扩容附带有检查容量,在为顺序表添加数据时,需要先检查容量,容量大于实际数据个数时,才可以插入新数据,扩容的同时要更新数组地址和容量大小
打印顺序表
void SLprinf(SeqList* s1)
{
assert(s1);
int i = 0;
for (i = 0; i < s1->size; i++)
{
printf("%d->", s1->arr[i]);
}
printf("\n");
return;
}
将顺序表的指针传入,assert防止空指针,后文就不再赘述,for循环遍历整个顺序表,依次打印数据
在指定位置增加数据
void SLinsert(SeqList* s1, int pop, datetype a)
{
assert(s1);
check_capicity(&s1);//检查容量,是否可以插入数据
int i = s1->size - 1;
for (i = s1->size - 1; i >= pop; i--)
{
s1->arr[i + 1] = s1->arr[i];
}
s1->arr[pop] = a;//插入数据
s1->size++;//有效数据加1
}
将数据向后移,空出下表为pop的位置,如下图
变量i的条件怎么确定呢?
下标为size的位置是空的,size-1是最后一个数据
执行顺序如下
- 将size-1的数据移动到size
- 将size-2的数据移动到size-1
- …
- 将pop的数据移动到pop+1
第一个移走是size-1所以将i的初始值赋为size-1
最后移走的是下表为pop的数据,于是结束标志就是i<=pop,保证pop处的数据移到pop+1
还有两种特殊位置的插入,这里给出代码供读者参考
从头部插入数据
void pushfront(SeqList* s1, datetype a)
{
assert(s1);
check_capicity(&s1);
int i = s1->size;
for (i = s1->size; i >= 1; i--)
{
s1->arr[i] = s1->arr[i - 1];
}
s1->arr[0] = a;
s1->size++;
}
从尾部插入
void pushback(SeqList* s1, datetype a)//在顺序表尾部添加数据
{
assert(s1);
check_capicity(&s1);
s1->arr[s1->size] = a;
s1->size++;
}
删除指定位置的数据
void SLErase(SeqList* s1, int pop)
{
assert(s1);
assert(s1->size!=0);
int i = 0;
for (i = pop; i <= s1->size - 2; i++)
{
s1->arr[i] = s1->arr[i+1];
}
s1->size--;
}
将pop后的数据移到pop上覆盖数据即可,如下图
变量i的确定和上文一样,在覆盖之后,有效数据减少一个
同样也有两种特殊位置的删除
删除尾部数据
void popback(SeqList* s1)
{
assert(s1);
assert(s1->size!=0);
s1->size--;
}
直接将size减1,下次添加该位置的数据时会直接覆盖
删除头部数据
void popfront(SeqList* s1)
{
assert(s1);
assert(s1->size!=0);
for (int i = 0; i <= s1->size - 2; i++)
{
s1->arr[i] = s1->arr[i + 1];
}
s1->size--;
}
查找某个数据是否存在</font
int SLfind(SeqList* s1, datetype a)
{
assert(s1);
int i = 0;
for (i = 0; i < s1->size; i++)
{
if (s1->arr[i] == a)
{
return i;
}
}
return -1;
}
遍历数组
找到返回下标,找不到返回-1
对指定数据进行修改
void modify(SeqList* s1, int pos, datetype a)
{
assert(s1);
assert(pos >= 0 && pos < s1->size);
s1->arr[pos] = a;
}
注意:传入的下标要是有效下标,注意越界
销毁顺序表
void Destroy(SeqList* s1)
{
assert(s1);
free(s1->arr);
s1->arr = NULL;
s1->capicity = 0;
s1->size = 0;
}
将arr释放,并置空,防止野指针
capicity,size置为0;
以上就是顺序表的详细讲解,创作不易,请各位不要吝惜三连,若有错误之处,还请各位不吝赐教。