目录
1.顺序表的概念及结构
1.1线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。
线性表是⼀种在实际中⼴泛使 ⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储
2.顺序表的分类
顺序表底层就是数组,是对数组的封装,实现了常见的增删查改等接口
顺序表有动态表和静态表两个分类
静态表直接给定空间大小,动态表能够灵活的申请空间,本文来介绍动态顺序表的实现
//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL s);
//扩容
void SLCheckCapacity(SL* ps);
//头部插入删除 / 尾部插入删除
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
//指定位置之前插入/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
int SLFind(SL* ps, SLDataType x);
3.顺序表的实现
要想实现能够对一个数组的大小进行灵活的空间大小开辟我们想到了结构体中的内容,柔性数组
我们先给出一个包含柔性数组的结构体,我们在这个结构体的基础上来实现我们的顺序表
这个顺序表中包含了size 有效空间个数
capacity 空间容量
arr 未开辟空间的柔性数组
typedef int SLDataType;//对变量进行重命名,便于我们对代码的修改和维护
typedef struct SeqList
{
SLDataType* arr;
int size; // 有效数据个数
int capacity; // 空间容量
}SL;
3.1初始化和销毁
初始化我们在想应该是怎样进行初始化,动态表是动态申请内存的,我们在没有使用的时候有效位数,空间大小就是0,数组指针就指向NULL,这样我们就完成了对顺序表的初始化
动态表需要我们使用内存函数进行空间的申请,所以我们要对动态申请的空间进行释放,防止内存泄漏,在释放空间之前我们要判断指针的指向是否为NULL,若是释放指向NULL的指针就会出现错误,最后将有效数据个数size和空间大小capacity置为0,这样就完成了顺序表的销毁
//初始化
void SLInit(SL* ps)
{
ps->arr= NULL;
ps->size = 0;
ps->capacity = 0;
}
//顺序表的销毁
void SLDestroy(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
3.2头插尾插
顺序表要想进行数据的插入我们首先要判断空间的大小是否足够,我们是否还能插入数据,若是空间不够我们应该怎么进行扩容,由于多处会用到对空间大小的判断以及扩容我们将这部分代码进行封装,以便我们后续的使用
/封装判断容量够不够 扩容
void SLCheckCapacity( SL* ps)
{
if (ps->capacity == ps->size)
{
//申请空间
//malloc realloc calloc 使用哪一个开辟空间?
//在空间不够的时候我们需要去增容,所以使用realloc
//先判断一下
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * 2 * sizeof(SLDataType));
//此时不能直接用ps来接收空间,要先保证空间开辟成功后我们才能将空间给指针
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);//直接退出程序不再执行
}
//申请多大的空间??
//增容通常是成倍数的增加,二倍/三倍
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
这里的代码我们首先进行有效数据个数和空间大小的判断,若是size==capacity则说明此时空间已经是满的,或者还没有进行空间的开辟,我们第一步使用三目操作符进行判断,若是空间大小capacity==0就是还没有进行空间开辟,返回4,然后使用realloc开辟一个空间大小为4的数组。
需要说明的是,每次扩容的时候我们都是将空间扩容到原来的两倍,这里不是随意地扩容,是有根据的扩容,感兴趣的可以百度一下为什么总是扩容到原来的二倍
在申请空间成功后我们就需要改变空间大小capacity的值capacity=newCapacity
3.2.1尾插
在判断空间是否足够后我们进行尾插,尾插就是在顺序表的尾部进行插入数据的操作
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
//插入之前先看空间够不够
SLCheckCapacity(ps);
ps->arr[ps->size++] = x; //这里的size++是后置++,在进行插入之后我们的有效数据加一,size+1
}
3.2.2头插
区别于尾插的地方就是我们需要把整个数组的所有数据后移一位之后再将我们的数据进行插入到ps->arr[0]的位置就完成了插入的操作,但是不要忘记我们的有效数据位数size也要进行++
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
//数据后移1位
for (int i = ps->size; i > 0 ; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->size++;
ps->arr[0] = x;
}
3.3头删尾删
3.3.1尾删
尾删就是将最后一个数据删除,我们可以直接将这个数据抛弃,size--将最后一个数据变成无效的数据再次进行操作的时候这个数据就会被覆盖
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);//保证顺序表不为空
ps->size--;
}
注意:有效数据个数size最小是0,当size=0的时候,顺序表已经是空的了,我们已经不能再进行尾删操作了,所以尾删要保证顺序表不为空
3.3.2头删
头删就是将顺序表中的第一个数据删除,然后将顺序表整个前移,然后将有效数据个数size--就完成了操作,这里我们直接将数据前移就可以直接覆盖第一个数据
void SLPopFront(SL* ps)
{
assert(ps);
for (int i = 0; i<ps->size-1 ; i++)
{
ps->arr[i] = ps->arr[i + 1];//
}
ps->size--;
}
3.4指定位置前插入/删除数据
3.4.1在指定位置前插入数据
我们需要传入参数有,顺序表指针,指定的位置,要插入的数据
增加数据就要判断空间大小是否足够,然后将指定位置之后的数据后移,最后将数据插入指定的位置,整个过程就结束了
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size; i>=pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos-1] = x;
//由于是在指定位置之前插入数据,所以我们这里pos-1
ps->size++;
}
3.4.2删除指定位置之前的数据
直接将指定位置之后的数据前移,将指定为位置的数据直接覆盖
void SLErase(SL* ps, int pos)
{
assert(ps);
SLCheckCapacity(ps);
for (int i = pos-1; i<ps->size; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
顺序表代码没有什么特别难得地方,主要就是很多细节要注意