一 顺序表的定义
1 概念
顺序表是一段连续物理地址存储单元存数据元素的线性结构(在逻辑上是连续存储的就是线性表:比如链表 栈 队列等)。一般情况下用数组存储,在数组上完成数据的增删查改。
连续的物理地址说明每个元素再内存中时挨个存放着的。这一点有别于链表。
图示:
二 顺序表的操作
①结构
为了方便顺序表能够动态存储数据(扩容),因此如此定义顺序表的结构:
用一个结构体,结构成员分别设置一个指针、一个size和一个capacity
·指针:指向这一块连续存储的空间
·size:存储的数据的个数
·capacity:实际能存储元素的多少
size和capacity:为了实现动态效果,size和capacity通常配合使用
//定义顺序表的结构
typedef int SLDataType;//存储的数据类型 假设是int类型的数据
typedef struct SequenceList
{
SLDataType* a;//动态开辟
int size;//数组的大小
int capacity;//数组的容量 方便进行容量方面的检查
}SL;//取名方便后续使用
关于typedef的两个用途:
a.方便后续更改存储的数据的类型
b.取别名,方便使用
②操作
1 初始化
注意:涉及到解引用操作的要对指针进行判断。防止对无意义的指针进行解引用。
这里初始化capacity先设置成0,也可以设置成指定大小。
void InitSL(SL* sl) {
assert(sl);//由于后续需要解引操作 先进行判定 该指针不能为空
sl->a = NULL;
sl->size = sl->capacity = 0;
}
2 销毁
主要是为了进行内存清理
void DestroySL(SL* sl){
assert(sl);//涉及对指针解引用都判断一下
//避免对空指针的再次释放
if (sl->a) {
free(sl->a);
sl->a = NULL;
sl->size = sl->capacity = 0;
}
}
3 打印
遍历即可。主要是为了test
void PrintSL(SL* sl){
assert(sl);
for (int i = 0; i < sl->size; i++) {
printf("%d ", sl->a[i]);
}
printf("\n");
}
由于插入操作有可能涉及到一个问题:容量不够。因此就需要对容量进行检查。由于后续不管头插尾插还是任意位置插入都需要进行检查,因此把这个功能抽象成一个接口函数方便复用。
void CheckCapacity(SL* sl) {
//容量检测
//如果插入之后 存储的元素大于数组的容量 重新进行定义开辟(size==capacity说明再插入就越界了)
if (sl->size == sl->capacity) {
int newCapacity = sl->capacity == 0 ? 4 : 2 * sl->capacity;//1 一开始初始化容量为0 2:后续容量不够
SLDataType* tmp = (SLDataType*)realloc(sl->a, newCapacity * sizeof(SLDataType));
assert(tmp);//1 原地扩容 2 异地扩容 3 失败返回NULL 差错处理
//将sl重新赋值
sl->capacity = newCapacity;
sl->a = tmp;
}
}
要进行扩容操作的话需要对capacity和a重新进行赋值。
newCapacity为新扩容之后的容量大小:需要考虑到一开始初始化为0以及后续扩容的情况。我们扩容一般是二倍扩容,如果一开始是0的情况没有考虑的话,两倍0是没意义的
tmp为新扩容之后的地址空间:需要用realloc进行扩容,有这几个点需要注意。第一个参数需要传入需要进行扩容的地址空间;第二个参数需要传入重新定义大小的字节数;返回值是void*需要进行转换。需要判断扩容是否失败
4 头插
数据整体都要往后移动一个位置,并且是从后往前进行移动的。
开始的位置是size-1(下标比元素个数小一个,最开始的下标为0的位置也需要移动)
void PushFrontSL(SL* sl, SLDataType x) {
assert(sl);
CheckCapacity(sl);
//数组整体往后移动一个位置
for (int i = sl->size - 1; i >= 0; i--) {
sl->a[i + 1] = sl->a[i];
}
//插入新的元素
sl->a[0] = x;
sl->size++;
}
5 尾插
void PushBackSL(SL* sl, SLDataType x) {
assert(sl);
CheckCapacity(sl);
//插入
sl->a[sl->size] = x;
sl->size++;
}
6 任意位置插入
和头插移动的思路有点类似,不过由于是pos位置插入,因此需要将pos连同之后的元素整体往后移动一个位置
void InsertSL(SL* sl, SLDataType x,int pos) {
assert(sl);
assert(pos >= 0 && pos <= sl->size);
CheckCapacity(sl);
//移动
for (int i = sl->size - 1; i >= pos; i--) {
sl->a[i + 1] = sl->a[i];
}
//插入
sl->a[pos] = x;
//更新size
sl->size++;
}
7 头删
头删需要考虑的情况比较复杂
对于删除,都是要求有元素才可以进行删除。如果size为0就不能删除了
除此之外,是移动进行删除的,那么size为1 的位置需要进行特殊的处理
就是移动进行删除,但是删除的话是从前往后移动数据进行删除
void PopFrontSL(SL* sl) {
assert(sl);
assert(sl->size > 0);//只有大于0才能进行删除
//处理为1的情况
if (sl->size == 1) {
sl->size--;
return;
}
for (int i = 1; i < sl->size; i++) {
sl->a[i-1] = sl->a[i];
}
sl->size--;
}
8 尾删
尾删比较简单
void PopBackSL(SL* sl) {
assert(sl);
assert(sl->size > 0);
sl->size--;
}
9 任意位置删除
任意位置删除的话,需要注意pos的范围。
void EraseSL(SL* sl,int pos) {
assert(sl);
assert(pos >= 0 && pos <= sl->size - 1);
for (int i = pos; i < sl->size; i++) {
sl->a[i] = sl->a[i + 1];
}
sl->size--;
}
10 查找某一元素在顺序表中是否存在
遍历即可
int FindSL(SL* sl, SLDataType x) {
assert(sl);
for (int i = 0; i < sl->size; i++) {
if (sl->a[i] == x) return i;
}
return -1;
}
11 对顺序表某一位置的数据进行修改
void ModifySL(SL* sl, SLDataType x, int pos) {
assert(sl);
assert(pos >= 0 && pos <= sl->size);
sl->a[pos] = x;
}