即使你内心没有一尊明月,也要给自己留下一方皎洁
文章目录
什么是顺序表
顺序表的实现
顺序表内部基础设置
结构体数据类型重定义
顺序表结构定义
顺序表空间初始化及扩容设置
顺序表空间的初始化及销毁
顺序表的扩容
顺序表基本功能
尾插尾删
头插头删
在任意位置删和插
大家好,我是纪宁。
本文将介绍顺序表的内容,此内容为数据结构的入门内容,可以为后面的链表等困难数据结构的学习做铺垫。希望大家和我都可以将数据结构的基础扎实!
有需要的老铁可以去看博主的往期作品 数据结构与算法对程序员的重要性http://t.csdn.cn/muEFA
本文使用的顺序表类型名和函数名如下表
SeqList | 顺序表 |
SLDataType | 重定义顺序表成员数据类型 |
SLInit | 初始化顺序表 |
SLDestroy | 销毁顺序表 |
SLPrint | 打印顺序表 |
SLCheckCapacity | 检查顺序表容量 |
SLPushFront | 头插 |
SLPopFront | 头删 |
SLPushBack | 尾插 |
SLPopBack | 尾删 |
SLInsert | 在pos位置插入 |
SLErase | 删除pos位置的成员 |
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,通俗的来讲就是数组存储,在数组的基础上多了增删查改的功能。
一般顺序表分为静态顺序表和动态顺序表。静态顺序表采用定长数组存储元素的形式,动态顺序表使用动态开辟的数组存储。因为顺序表只适用于已知需要存储多少数据的形式,而到底数组长度 N 空间开辟多大难以把握(开辟太大导致空间浪费,太少导致不够用)。所以我们一般使用的是动态顺序表,可以动态分配内存大小。
顺序表的基础设置中包括顺序表结构体的定义和结构体数据类型的重定义,结构开辟的动态数组空间是由 SLDataType*(重定义)类型的指向顺序表成员的指针和 表示顺序表最大空间的变量 capacity 共同维护的。
typedef int SLDataType;
将顺序表内的数据类型重定义,可以方便顺序表成员数据类型的修改。如果要修改结构体变量数据类型(如由 int 变为 double)时,只需要修改 typedef 后面的数据类型,那么程序中顺序表的元素类型变为 double 型。
typedef struct SeqList
{
SLDataType* arr;
int sz;
int capacity;
}SL;
arr是一个指针,指向动态开辟的顺序表数组,数据类型为 typedef 重定义后的数据类型,方便修改;sz表示顺序表元素个数;capacity表示顺序表的最大空间。
void SLInit(SL* s)
{
s->arr = (SLDataType*)malloc(4 * sizeof(SLDataType));
if (s->arr == NULL)
{
perror("malloc");
exit(-1);
}
s->sz = 0;
s->capacity = 4;
}
首先,可以使用 malloc 函数开辟 k 个成员的空间,并且将首空间的地址强转后赋值给 arr 指针,同时将最大空间变量 capacity 赋值为4,但顺序表成员个数变量 sz 依旧是0,因为只是初始化了顺序表空间,并未往顺序表中加入成员。
在程序结束的阶段,在保存数据后,最好做到能将顺序表销毁。
void SLDestroy(SL* s)
{
free(s->arr);
s->arr = NULL;
s->sz = s->capacity = 0;
}
销毁阶段将初始化时开辟的内存释放,并将指针置空,同时成员个数 sz 和 最大空间 capacity 的值都改为0。
因为动态的顺序表初始状态下只开辟了较少的空间,在程序判断出顺序表内存空间已满后要进行顺序表空间的扩容处理,所以最好可以封装一个函数,达到既可以判断最大空间与当前成员的关系(即判断满没满),又能在将顺序表扩容。
void SLCheckCapacity(SL* s)
{
if (s->sz == s->capacity)
{
SLDataType* tmp = realloc(s->arr, 2 * sizeof(SLDataType) * (s->capacity));
if (tmp == NULL)
{
perror("realloc");
exit(-1);
}
else
{
s->arr = tmp;
s->capacity *= 2;
}
}
}
使用 realloc 函数可以将顺序表扩容,因为 realloc 每扩容一次都需要耗费一定的资源,那么如果使用过于频繁,势必会让内存的压力变得非常大,所以一般顺序表扩容都是以2倍的速度增长。realloc 函数扩容动态内存分为原地扩容和异地扩容,可以先用一个指针来接受扩容后的首地址,再赋值给 arr,同时顺序表的最大空间乘2。
顺序表的基本功能为头插、头删、尾插、尾删。
void SLPushBack(SL* s, SLDataType x)
{
SLCheckCapacity(s);
*(s->arr + s->sz) = x;
s->sz++;
}
void SLPpoBack(SL* s)
{
if (s->sz == 0)
{
exit(-1);
}
s->sz--;
}
顺序表尾插之前要检查顺序表是否需要增容,然后将元素插在 arr[sz] 的位置即可,尾删的时候需要注意如果元素个数为0,就要强制要退出或断言。
void SLPushFront(SL* s, SLDataType x)
{
SLCheckCapacity(s);
if (s->sz == 0)
{
s->arr[0] = x;
s->sz++;
}
else
{
int i = 0;
for (i = s->sz-1; i>=0; i--)
{
*(s->arr + i+1) = *(s->arr + i);
}
*(s->arr) = x;
s->sz++;
}
}
void SLPopFront(SL* s)
{
if (s->sz == 0)
exit(-1);
else
{
int i = 0;
for (i = 0; i < s->sz - 1; i++)
{
s->arr[i] = s->arr[i + 1];
}
s->sz--;
}
}
头插的时候要判断元素个数,如果没有成员,就直接将首元素赋值为 x ;如果有成员,就先将所有成员后移,再首元素赋值为 x,最后将元素个数 sz 加1。
头删的时候只需要将所有成员前移一个位置,再将元素个数 sz 减1。
void SLInsert(SL* s, int pos, SLDataType x)
{
s->sz++;
int i = 0;
for (i =s->sz-1-1; i>=pos; i--)
{
*(s->arr + i + 1) = *(s->arr + i);
}
*(s->arr + pos) = x;
}
void SLErase(SL* s, int pos)
{
if (s->sz < pos)
exit(-1);
for (int i = pos; i < s->sz - 1; i++)
{
*(s->arr + i) = *(s->arr + i + 1);
}
s->sz--;
}
在任意位置插入数据和删除数据其实原理和头插头删是一样的,在任意位置插和删只需要把握部分整体后移的‘度’就行,建议新手多画图来理解这种较为复杂的情况,尽量不要出现“代码5分钟,调试两小时。”的情况。