目录
前言
在管理用户信息和客户时我们常常需要进行增加,删除,修改,查找等操作,因此如何实现这一操作非常重要,让我们一起来看一下如何利用顺序表实现
提示:以下是本篇文章正文内容,下面案例可供参考
一、顺序表的概念及结构
-
概念
顺序表是线性表的一种,而线性表是n个具有相同特征的数据元素的有序数列
注意:顺序表的底层结构是数组,对数组的封装,实现了常用的增删查改等接口
-
分类
- 静态顺序表
特点:使用定长数组存储元素
//定义顺序表的结构
//使用define是为了方便后续修改数据
#define N 100
//静态顺序表
struct seqlist {
int arr[N];
int size;//记录当前的有效数据个数
};
-
- 动态顺序表
特点:按需申请
//动态顺序表
//把数据类型换名为SQL,方便以后替换
typedef int SQL;
typedef struct seqlist {
SQL* arr;
int size;//有效数据大小
int capacity;//记录当前空间大小
}SL;
//给结构体类型重命名
typedef struct seqlist SL;
二、动态顺序表的实现
1.动态顺序表的初始化和销毁
在进行顺序表的初始化前,我们要先创建一个头文件“seqlist.h”方便后期我们引用顺序表,再在头文件中创建一个“seqlist.c”文件。我们在.h文件中声明我们实现顺序表功能的函数,在.c文件中编写具体的函数,再在源文件中创建一个测试文件test.c判断函数实现是否正确。
原因如下:
- .h头文件用于声明数据结构(如顺序表的结构定义、函数原型等),相当于提供了该模块的接口。这样做的好处是将接口与实现分离,使得代码更加清晰,易于理解和维护。
- 当修改顺序表的实现(.c文件)时,只要接口(.h文件)未变,依赖该顺序表的其他模块不需要重新编译。
- 通过在不同的.c文件中包含同一个.h文件,可以轻松地在多个地方复用顺序表的功能,而无需重复编写相同的功能声明。
- 如果函数或变量的定义直接放在头文件中,并且多个源文件包含了这个头文件,编译时会导致多重定义错误
-
初始化
//顺序表的初始化
void SLInit(SL* ps) //传地址
{
ps->arr = NULL;
ps->size = ps->capacity = 0;//将开始的内存大小和数据数量都置为0
};
注意:此处函数的传参不能传值,因为我们还未给新建变量赋值
-
销毁
//顺序表的销毁
void SLDestroy(SL* ps)
{
//空指针不能解引用,需要用if判断
if (ps->arr)//这是判断arr是否指向空
{
free(ps->arr);
}
ps->arr = NULL;//释放地址之后为了避免它变成野指针要置为NULL,避免内存泄漏
ps->size = ps->capacity = 0;
}
2.数据的增删查改
-
增加数据
-
尾增
从顺序表最后一个数据后插入新的数据:
具体思路:
首先我们要判断内存空间是否足够,不够我们需要先进行增加,然后将数据插入,相应的数据个数和存储空间变化
注意:由于无论如何插入数据,我们都要进行内存空间判断的这一操作于是我们写一个函数专门用来判断
//加入一个函数专门用来判断内存空间够不够
void SLCheckCapacity(SL* ps)
{
//直接利用结构体数组的地址
//插入数据之前先看看数据够不够
if (ps->size == ps->capacity)
{
//申请空间,利用relloc函数调整空间的大小
//增容一般是2/3倍的去增容
int newcapacity = ps->capacity == 0 ? 4 : 2 * (ps->capacity);//如果没有空间就先给他四个,如果有的话就扩容两倍
//创建新的变量判断内存申请是否成功,防止数据丢失
SQL* temp = (SQL*)realloc(ps->arr, newcapacity * sizeof(SQL));//注意realloc这里是增加字节数
//这里不能使用malloc和calloc,因为他们没有实现函数空间增减的功能
if (temp == NULL)
{
perror("realloc fail!");
exit(1);
}
//空间申请成功
ps->arr = temp;
ps->capacity = newcapacity;
}
};
尾插函数的实现:
//尾插函数的具体实现,将x插入
void SLPushback(SL* ps, SQL x)
{
assert(ps);//防止客户填入意想不到的值如:NULL导致程序崩掉
SLCheckCapacity(ps);
/*ps->arr[ps->size] = x;
++ps->size;*/
ps->arr[ps->size++] = x;
};
-
头增
同理,头插就是从第一个数插入,然后后面的数都相应的往后移动即可
//头插-从前面插入数据
void SLpushfront(SL* ps, SQL x)
{
SLCheckCapacity(ps);
//先让数据表的每一位向后移动一位
for (int i = ps->size; i; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
};
-
删减数据
-
尾删
从后面删除数据,我们只需要对size进行相应的变化即可,capacity仍然保持不变
注意:ps不能为NULL,否则会报错;同样的顺序表也不能为空,不然我们这么做没有意义
//尾删
void SLPopBack(SL* ps) {
assert(ps);
assert(ps->size);//判断顺序表不能为空
--ps->size;//size之外的数据不影响增删查改,直接--size即可
}
-
头删
头删即删除第一个数据,后面的数据都向前一个即可
//头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);
//判断顺序表不能为空
for (int i = 0; i < (ps->size) - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size;
}
总结
顺序表作为数据结构,凭借内存连续、支持随机高效访问的优点,适用于静态或小规模数据集的存储与快速查询,但插入删除操作效率较低,需根据应用场景权衡使用。