顺序表是线性表的一种,其逻辑结构和物理结构均连续。
顺序表的底层是使用数组来存储数据的,其结构可以定义为
typedef struct SeqList
{
SLDataType* a;//指向动态开辟的数组
int size;//有效数据个数
int capacity;//容量空间的大小(即动态开辟出来的空间的大小)
}SL;
这里的数组使用动态开辟的方式来定义,方便后续空间的扩容。
所以初始化为
void SeqListInit(SL* ps)//开辟空间并把size赋值为0
{
ps->a = (SLDataType*)malloc(sizeof(SLDataType) * 4);//给数组a开辟4个SLDataType大小的空间
if (ps->a == NULL)
{
printf("内存申请失败\n");
exit(-1);
}
ps->size = 0;
ps->capacity = 4;
}
void SeqListDestory(SL* ps)//销毁是销毁开辟的空间,结构体变量s随着程序的结束而被销毁
{
if (ps->a)//a不为空才free,free(NULL)什么都不做,没必要销毁
{
free(ps->a);
}
ps->a = NULL;
ps->size = ps->capacity = 0;
}
在将数据放入顺序表时,需要检查顺序表是否满了,如果满了则需要扩容,而尾部插入,头部插入,任意位置的插入都需要用到检测是否满了,所以可以直接将检测是否满了写成一个函数。
void SeqListCheckCapacity(SL* ps)
{
assert(ps);
//如果满了就扩容
if (ps->size >= ps->capacity)
{
ps->capacity *= 2;
ps->a = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity);
if (ps->a == NULL)
{
printf("扩容失败\n");
exit(-1);
}
}
}
这里扩容一般是将数组的扩容成1.5倍或2倍,这样既不会造成空间浪费,也不会造成经常需要扩容
然后是尾部插入的函数
void SeqListPushBack(SL* ps, SLDataType x)
{
assert(ps);
SeqListCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
//可以直接利用任意位置的插入
//SeqListInsert(ps,ps->size,x);
}
将需要放入的值x放进数组中下标为size的位置
在顺序表的头部插入一个元素,此时涉及到将顺序表中所有的值往后挪动一位
void SeqListPushFront(SL* ps, SLDataType x)
{
assert(ps);
SeqListCheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
//可以直接利用任意位置的插入
//SeqListInster(ps,0,x);
}
前面的尾插和头插是插入操作的特殊情况,所以也可以直接使用任意位置插入的函数对尾插和头插函数进行实现,具体如何实现,在前面两个函数的代码中已经说明
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos < ps->size && pos >= 0);//当pos=ps->size时就是尾插,当ps=0时就是头插
SeqListCheckCapacity(ps);
int end = ps->size-1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
}
ps->a[pos] = x;
ps->size++;
}
要注意并不是真正的删除,而是让最后一个元素无法被访问
void SeqListPopBack(SL* ps)
{
assert(ps);
ps->size--;
//注意此时删除后的元素仍在,但size变小了,故无法访问了
//如1 2 3 4 5删除后仍是1 2 3 4 5,但5无法被访问
//可以直接利用任意位置的删除
//SeqListErase(ps,ps->size-1);
}
此时也不是真正的删除,而是将下标为1到size-1的元素全部往前移动一位,将下标为0的元素覆盖掉,如何size--,这样子最后一位的就无法被访问了
void SeqListPopFront(SL* ps)
{
assert(ps);
int start = 0;
while (start < ps->size - 1)
{
ps->a[start] = ps->a[start + 1];
++start;
}
ps->size--;
//注意此时不是删除了第一个元素,而是将第一个元素覆盖掉了,且原本的最后一个元素没有动,但是因为size--了,所以无法访问
//如1 2 3 4 5头删后变成2 3 4 5 5
//可以直接利用任意位置的删除
//SeqListErase(ps,0);
}
前面的尾删和头删是删除操作的特殊情况,所以也可以直接使用任意位置删除的函数对尾删和头删函数进行实现,具体如何实现,在前面两个函数的代码中已经说明
void SeqListErase(SL* ps, int pos)
{
assert(ps);
assert(pos < ps->size && ps >= 0);//当pos=ps->size时就是尾删,当ps=0时就是头删
int start = pos;
while (start < ps->size - 1)
{
ps->a[start] = ps->a[start + 1];
++start;
}
ps->size--;
}
找到了就返回所找数据的下标,通过查找操作,就可以对数据进行修改
int SeqListFind(SL* ps, SLDataType x)
{
assert(ps);
int i = 0;
while (i < ps->size)
{
if (ps->a[i] == x)
{
return i;
}
i++;
}
return -1;
}
顺序表缺陷:①中间或头部的插入删除操作很慢,需要挪动数据,时间复杂度为O(N)
②空间不够时,增容会有一定消耗和空间浪费
顺序表优点:①能够随机访问,便于查找和排序
②缓存命中率高(物理空间连续)