顺序表是数据结构的基础,是进入数据结构的第一个台阶,逻辑并不复杂,所用的知识也是C语言的基础知识
这篇文章,我们将具体介绍顺序表的基本用法,希望这篇文章能够让你掌握顺序表。
目录
概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表可分为两种
1.静态顺序表:使用定长数组储存元素
2.动态顺序表:使用动态开辟的数组存储
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表
动态顺序表的函数接口
// 基本增删查改接口
// 顺序表初始化
void SeqListInit(SeqList* psl);
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl);
// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* psl);
// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* psl);
// 顺序表查找
int SeqListFind(SeqList* psl, SLDataType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos);
// 顺序表销毁
void SeqListDestory(SeqList* psl);
// 顺序表打印
void SeqListPrint(SeqList* psl);
函数具体实现
1.顺序表初始化
初始化即让顺序表中的所有变量置为对应的空值
void SeqListInit(SL* ps)
{
ps->a = NULL;
ps->capacity = ps->size = 0;
}
2.检查容量,扩容函数
当结构体中capacity 和 size 相等的时候即为容量满的情况,此时也要分为两种情况,
第一种是capacity = 0,那么说明顺序表处于初始化状态,我们需要为其malloc开辟空间,我们先为其开辟4个单位的空间
第二种是capacity!=0,那么说明顺序表已经装满,那么我们每次扩大容量至原先的两倍
根据上面的分析,我们可以直接用三木操作符算出新的容量大小newcapacity
再使用realloc函数开辟空间,realloc在空间为空的时候可以直接当作malloc使用
当然我们也要检查,是否开辟成功,若不成功则退出程序,避免出现更大的错误
代码如下
void SeqListCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
TYPE* tmp = (TYPE*)realloc(ps->a, newcapacity * sizeof(TYPE));
if (tmp == NULL)
{
printf("%s\n", strerror(errno));
return;
}
ps->a = tmp;
ps->capacity = newcapacity;
//printf("扩容成功\n");
}
}
3.尾插
尾插的时候,我们需要先检查容量是否已满,及时的进行扩容
由于size就是元素的下一个位置,所以我们直接将新的数据插入到下标为size的地方,再将size++即可
代码如下
void SeqListPushBack(SL* ps, TYPE x)
{
SeqListCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
4.头插
头插对于顺序表来说比较复杂,需要先记录end节点,再利用while循环将数据一一向后挪动,再将新数据插入到首位,时间复杂度为O(n),最后不要忘记将size++
代码如下
void SeqListPushFront(SL* ps, TYPE x)
{
SeqListCheckCapacity(ps);
int end = ps->size-1;
while (end>=0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
5.尾删
尾删很简单,首先需要用assert断言确保顺序表不为空,否则会破坏顺序表结构
然后将size--即可,这样最后一个数据就无法访问到,或者会被下一个数据所覆盖
代码如下
void SeqListPopBack(SL* ps)
{
assert(ps->size > 0);
ps->size--;
}
6.头删
头删对于顺序表也是比较复杂的
首先判断顺序表是否为空,再利用while循环,将顺序表的数据依次向前挪动一位,将首位元素覆盖即可,最后将size--;
时间复杂度也尾O(n)
代码如下:
void SeqListPopFront(SL* ps)
{
assert(ps->size > 0);
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--;
}
7.查找
顺序表的查找即用循环语句,遍历整个顺序表,找到对应元素则返回下标,找不到即返回-1
int SeqListFind(SL* ps, TYPE x)
{
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
8.定向插入数据
向pos位置插入数据,首先需要检查容量是否已满
下来我们需要判断pos位置,分为三种情况
如果超出size范围,如果超出size范围,则提示信息并返回
如果pos位置等于size,那么即实现尾插的作用即可
如果小于size,那么逻辑就与头插类似,需要创建end尾部变量,再将end之前pos以后的元素,依次向后挪一个单位,再将pos位置插入新元素即可
最后别忘记让size+1;
代码如下:
void SeqListInsert(SL* ps, int pos, TYPE x)
{
assert(pos >= 0);
SeqListCheckCapacity(ps);
if (pos > ps->size)
{
printf("超出范围\n");
exit(-1);
}
else if (pos == ps->size)
{
ps->a[ps->size] = x;
}
else
{
int end = ps->size - 1;
while (end>=pos)
{
ps->a[end+1] = ps->a[end];
end--;
}
ps->a[pos] = x;
}
ps->size++;
}
9.定向删除数据
定向删除数据和定向插入数据,逻辑就是反向为之,与头删类似,先要判断顺序表是否为空,再判断pos的范围,也分为三种情况,与定向插入大差不差,差别就在于当pos小于size的时候,这里实现的是头删逻辑,设置begin变量记录pos的位置,将pos后的数据依次向前挪动一个单位,最后size-1即可。
void SeqListErase(SL* ps, int pos)
{
assert(ps->size > 0);
if (pos > ps->size)
{
printf("超出范围\n");
exit(-1);
}
else if (pos == ps->size)
{
SeqListPopBack(ps);
}
else
{
int begin = pos;
while (begin < ps->size)
{
ps->a[begin] = ps->a[begin + 1];
begin++;
}
}
ps->size--;
}
10.顺序表的销毁
任何用malloc,realloc,calloc开辟的空间都是在堆上开辟的,需要我们手动释放,否则会出内存泄漏的问题,我们只需要用free释放空间,再将变量置为相应空值即可
代码如下
void SeqListDestroy(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
11.顺序表的打印
利用循环语句,从下标0开始到size下标,依次打印即可
void SeqListPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
}
总结
顺序表的实现,相对容易,代码和逻辑都比较简单,这只是数据结构的基础,希望这篇文章可以让你清晰的掌握顺序表的结构与逻辑!