文章目录
顺序表是一种线性表的存储结构,它是用一组地址连续的存储单元依次存储线性表中的数据元素 。顺序表中的数据元素可以用一个一维数组来表示 。
顺序表的优劣势
优:
- 支持随机访问既可以快速任意存取表中任一位置的元素
- 无需为表示表中元素之间的逻辑关系而增加额外的存储空间
劣:
- 插入和删除需要移动大量元素(时间复杂度较高)。
- 当线性表长度变化较大时,难以确定存储空间的容量。
- 易造成存储空间碎片。(扩容有风险)
所以当不需频繁插入和删除时,可以适当使用顺序表结构!
顺序表的定义、初始化、状态判断以及打印
顺序表说白了就是一个数组,只不过数组是一个静态的,而顺序表则是可以动态的,可以根据需求动态扩容。
定义
SLDateType * a
数据空间size
待插入下标,且也是当前已存数据的数量capacity
数据空间的最大容量。
typedef int SLDateType;//数据类型
typedef struct SeqList
{
SLDateType* a;//申请空间
int size;//顺序表的下标
int capacity;//数组空间容量
}SeqList;
初始化顺序表
这里申请空间 , 这里我们申请五个空间
void SeqListInit(SeqList* ps) {
assert(ps);
ps->a = (int*)malloc(5 * sizeof(int));
if (ps->a == NULL)
{
printf("Init:Error\n");
return;
}
ps->size = 0;
ps->capacity = 5;
}
- 申请五个空间 并更新
capacity
变量。 - 初始化
size
为0;
顺序表状态判断
既然是动态数组(顺序表)那么就会有申请的内存被用满的一天,那么在被用满时,再进行插入就十分危险且可能产生数组越界!!!。所以在进行顺序表操作时状态判断不可避免。
- 先判断
szie
变量是否等于capacity
变量,若等于则表示已无可用空间,这时就需要扩容。若不等于则返回。 - 扩容,一般扩容是原空间的两倍。
- 判断
size
是否大于capacity
,若大于说明顺序表发生错误。
void stateSeqList(SeqList* ps) {
if (ps->size == ps->capacity)
{
SLDateType* tmp = (SLDateType*)realloc(ps->a, 2 * (ps->capacity * sizeof(int)));
if (tmp == NULL)
{
printf("tmp:ptr NULL\n");
return;
}
ps->a = tmp;
ps->capacity *= 2;
}
if (ps->size - ps->capacity > 0)
{
printf("state:ERROE 1\n");
return;
}
}
顺序表的打印
void SeqListPrint(SeqList* ps) {
for (size_t i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
}
顺序表的增、删、查、改
插入
顺序表的常规插入有头插和尾插,两种插入方式虽只有一字之差,但所需的时间复杂度就有很大的变化。
头插
顺序表的头插需要挪动数据。
- 先判断顺序表状态。
- 开始挪动数据
- 插入数据并更新下标
时间复杂度:O(n)
void SeqListPushFront(SeqList* ps, SLDateType x) {
assert(ps);//判断指针是否传入成功
stateSeqList(ps);//状态判断
int end = ps->size - 1;//获取下标为挪动数据作准备。
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];//开始挪动数据
end--;
}
ps->a[0] = x;//插入数据
ps->size++;//更新最大下标。
}
尾插
与头插相比,尾删就很简单了只需要获取末尾坐标即可,插入数据并不需要挪动数据。且速度快
- 获取末尾坐标并插入
- 更新顺序表下标
void SeqListPushBack(SeqList* ps, SLDateType x) {
assert(ps);
stateSeqList(ps);
ps->a[ps->size] = x;//获取下标并插入
ps->size++;//更新下标
}
时间复杂度:O(1)
任意位置插入
只需往后挪动 n - pos个元素,即可在 pos 位置插入元素
- 往后挪动 n - pos 个数据
- 在
pos
位置插入数据 - 更新下标。
void SeqListInsert(SeqList* ps, int pos, SLDateType x) {
assert(ps);
assert(0 <= pos && pos <= ps->size);
stateSeqList(ps);
int end = ps->size - 1;
while (pos <= end)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
时间复杂度:
当pos
位置在顺序表中间和头部时 :O(n)
当pos
位置在顺序表尾时 :O(1)
任插函数的复用
头插:
void SeqListPushFront(SeqList* ps, SLDateType x) {
SeqListInser(ps,0,x);
}
尾插:
void SeqListPushBack(SeqList* ps, SLDateType x) {
SeqListInser(ps,ps->size,x);
}
删除
顺序表的常规删除同样与增加一样,分为头删和尾删,两者时间复杂不一样,头删也需要挪动空间。
头删
头删就是需要删除第一个元素并挪动后续数据
- 删除头部数据
- 挪动数据
size
下标自减(自减前的数据下次插入时覆盖)
void SeqListPopFront(SeqList* ps) {
assert(ps);
int end = 0;
while (end < ps->size - 1)
{
ps->a[end] = ps->a[end + 1];//挪动数据
end++;
}
ps->size--;//下标自减
}
时间复杂度:O(n)
尾删
尾删只需挪动坐标自减即可。(自减前的数据下次插入时覆盖)
void SeqListPopBack(SeqList* ps) {
assert(ps->size > 0);
ps->size--;
}
时间复杂度:O(1)
任意位置删除
只需要往前挪动pos位置后的元素即可。
- 挪动后续数据覆盖
pos
位置的数据 - 更新
size
自减
void SeqListErase(SeqList* ps, int pos) {
assert(ps);
assert(0 <= pos && pos < ps->size);
int end = pos;
while (end < ps->size - 1)
{
ps->a[end] = ps->a[end + 1];
end++;
}
ps->size--;
}
时间复杂度:
当pos
位置在顺序表中间和头部时 :O(n)
当pos
位置在顺序表尾时 :O(1)
任删函数的复用
头删:
void SeqListPopFront(SeqList* ps) {
SeqListErase(ps, 0);
}
尾删:
void SeqListPopBack(SeqList* ps) {
SeqListErase(ps, ps->size - 1);
}
查找
顺序表的查找就很简单了,顺序表支持随机访问,即只需下标即可。
int SeqListFind(SeqList* ps, int x) {
assert(0 <= x && x < ps->size);
return ps->a[x];
}
时间复杂度:O(1)
修改
既然支持随机访问,输入下标可以修改。
int SeqListRevise(SeqList* ps, int pos,SLDateType x) {
assert(0 <= x && x < ps->size);
return ps->a[pos] = x;
}
顺序表的销毁
将结构体中
a
的变量释放,其余变量赋值 -1 即可。
void SeqListDestroy(SeqList* ps) {
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = -1;
ps->size = -1;
}