1. 顺序表的概念及结构
1.1概念
顺序表是一种线性表,其特点是将逻辑上相邻的元素存储在物理位置上相邻的存储单元中。顺序表的存储结构是连续的存储空间,这使得顺序表具有随机访问的特性,即可以通过元素的序号直接访问对应的存储地址
1.1.1线性表
在逻辑上是线性结构(连续的一条直线),但是在物理结构上并不一定连续(可能像链表一样,地址并不相邻)。
1.2结构
顺序表的底层是数组,实际上是对数组的封装,同时需要记录顺序表中的元素个数。元素个数的类型是int,两个不同类型的数组可以用结构体存储。
所以顺序表实际上就是一个包含了数组和记录元素个数的结构体,同时实现了增删查改等接口。
1.3与数组的区别
用一个比喻来区分,一道油菜在普通饭店里面的名字可能就是油菜,而在米其林餐厅里面的名字就可能是绿野仙踪。普通饭店就相当于数组,米其林餐厅就好比顺序表。
2.顺序表分类
2.1静态顺序表
struct SeqList
{
int arr[100];//定长数组
int size; //数据个数
};
即数组是定长的,存在缺陷。
不知道需要多少空间,空间给少了不够用,给多了造成较多浪费。
2.2动态顺序表
struct SL
{
SLDataType* arr;//动态数组
int size;//有效数据个数
int capacity;//数组的容量
};
可以根据需要增加空间,对空间的浪费较少,所以应用较多。
3.顺序表的实现
3.1初始化
首先定义结构体
//顺序表的类型不止有int 用typedef便于后续修改
typedef int SLDataType;
//定义顺序表的结构
typedef struct SL
{
SLDataType* arr;//动态数组
int size;//有效数据个数
int capacity;//数组的容量
}SL;//用typedef对数组进行重命名
要注意 这里用SLDataType重命名int类型,是因为顺序表的类型不只有int一种,代码多了改起来很麻烦。
初始化
void SLInit(SL* sl)
{
sl->arr = NULL;
sl->size = 0;
sl->capacity = 0;
}
3.2插入数据
3.2.1尾插
首先有一个顺序表1,2,3,4,5 要将6插入尾部
初始有五个元素,所以size的值为5
刚好将6插入arr[5]的地方 之后size再加一
void SLPushBack(SL* sl, SLDataType x)
{
sl->arr[sl->size++] = x;
}
这里会有一个问题 如果没有剩余空间了怎么办?所以先要判断空间够不够用
(头插也需要判读空间 所以我把重复的代码封装成一个函数)
如果数组的容量与有效数据个数相同,说明数组空间满了
如果满了,用realloc函数对数组进行扩容。那么问题来了,扩容到多少呢?
如果一个空间一个空间扩容确实不会有空间剩余,但是会降低效率,所以每次我们扩容到两倍。
void SLCheckCapacity(SL* sl)
{
if (sl->size == sl->capacity)//有效数据和空间大小一样 表示被占满
{
//扩容
int newCapacity = (sl->capacity == 0) ? 4 : 2 * sl->capacity;
SLDataType* tmp = (SLDataType*)realloc(sl->arr, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
sl->arr = tmp;
sl->capacity = newCapacity;
}
}
注意初始空间为0时直接给4个。
3.2.2头插
这时需要把所有的元素向后移一位。
如果从前往后移动,那么元素会被覆盖,无法正确实现(试一下就OK了)。
所以从数组的最后开始移动,最后再给sl->arr[0]赋值成要插入的元素就可以了。
void SLPushFront(SL* sl, SLDataType x)
{
assert(sl);
//先判断空间是否足够
SLCheckCapacity(sl);
for (int i = sl->size; i > 0; i--)
{
sl->arr[i] = sl->arr[i - 1];
}
sl->arr[0] = x;
sl->size++;
}
3.2.3 指定位置之前插入
即需要将从指定位置开始,之后的数据,进行一个头插,方法和头插一样不再赘述。
void SLInsert(SL* sl, int pos, SLDataType x)
{
assert(sl);
//先判断空间是否足够
SLCheckCapacity(sl);
for (int i = sl->size; i > pos; i--)
{
sl->arr[i] = sl->arr[i - 1];
}
sl->arr[pos] = x;
sl->size++;
}
3.3 删除数据
3.3.1 尾删
遍历数组时,只在意有效数据,size减一就相当于把最后一个元素覆盖了。
void SLPopBack(SL* sl)
{
assert(sl);
sl->size--;
}
3.3.2 头删
与头插相同 ,需要把第二个以后的元素向前移动一位。
这时候就需要从头开始移动了。不要忘了size减一。
void SLPopFront(SL* sl)
{
assert(sl&&sl->size);
for (int i = 0; i < (sl->size - 1); i++)
{
sl->arr[i] = sl->arr[i + 1];
}
sl->size--;
}
3.3.3 删除指定位置的元素
将指定位置之后的数据全部向前移动一位就可以了。
void SLFrase(SL* sl, int pos)
{
assert(sl);
for (int i = pos; i < sl->size - 1; i++)
{
sl->arr[i] = sl->arr[i + 1];
}
sl->size--;
}
3.4 查找数据
int SLFind(SL sl, SLDataType val)
{
for(int i=0;i<sl.size;i++)
{
if (val == sl.arr[i])
return i;
}
return -1;
}
3.5 销毁顺序表
void SLDestroy(SL* sl)
{
if (sl->arr)
free(sl->arr);
sl->arr = NULL;
sl->size = 0;
sl->capacity = 0;
}
4. 完成
到这里就完成啦,第一次写还是很不熟悉,欢迎大家指出不足呀。有哪里不懂的欢迎评论区提问哦!!