文章目录
前言
一、顺序表的分类
顺序表和数组的区别
顺序表的底层结构是数组,对数组的封装,实现了常⽤的增删改查等接⼝
顺序表分为两类:
1.静态顺序表
typedef int SLDataTyep;
typedef struct SqList
{
SLDataTyep arr[MAX];//定长数组
int size;//数组长度
}SL;
这样就是顺序表的静态状态,但是他有个缺点就是数组的长度有限给多了就浪费空间,给少了,空间就不够用如果用在用户的信息储存方面的话,如果空间给少了就会造成用户的信息丢失,造成严重的后果,所以我们通常都会用动态顺序表。
2.动态顺序表
typedef int SLDataTyep;
typedef struct SqList
{
SLDataTyep* arr;
int size;//有效数据
int capacity;//空间容量
}SL;
动态顺序表与静态顺序表的区别就是:静态顺序表不需要申请空间,他是已经给好了空间的大小。然而动态顺序表这需要申请空间。
接下来我们将会从顺序表的创建顺序表,插入,删除,查找四个方面来学习动态顺序表
二、顺序表的创建
typedef int SLDataTyep;
typedef struct SqList
{
SLDataTyep* arr;
int size;//有效数据
int capacity;//空间容量
}SL;
//typedef struct SqList SL;这样命名与结构体后的SL一样
这里用typedef int SLDataTyep的好处就是可以帮助我们在以后修改代码的时候更方便。这里的意思就是SLDataTyep 跟int 是一样的效果。他的好处就是如果以后我们想把int 改为其他类型的时候。只把int 改成要改的类型就行。到时候就不用去代码里面一行一行的改了。大大的提高了效率。
1.顺序表的初始化
void SLIntit(SL* ps)
{
assert(ps);
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
顺序表的初始化还是挺简单的。以上就是代码。初始化嘛,就相当于给他赋一个值嘛。我这里只是习惯把他设为0.这里是可以改的哦。设为你喜欢的值就行了,这个要看个人习惯
2.顺序表的销毁
代码如下(示例):
void SLDestory(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
在这里需要注意些什么?其实呀,大多数的 小伙伴在这里把顺序表销毁用free函数销毁就ok。但是,这里一定不要忘记还要给成员变量赋为0哦。
三、 顺序表的插入
顺序表的插入呢一共三种:尾插,头插,在指定位置之前插入数据
1.尾插
我们来看看这张图。如果我们现在想插入一个数字6,这个就是数字插入6时个个变量的变化图,由此可以写出代码
void SLPushBank(SL* ps,SLDataTyep x)
{
assert(ps);
ps->arr[ps->size]=x;
++ps->size;
}
有此图可以我们还可以思考一个问题。当我们的这个有效数据个数(size)和空间容量大小(capacity)一样大时,当这时我们还要插入一个数时,那该怎么办呢?其实呀很简单,我们这时在给他申请一个数据空间就ok了,那该这样申请呢?请看代码:
void newcapacity(SL* ps)
{
assert(ps);
if (ps->capacity == ps->size)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataTyep* tmp = (SLDataTyep*)realloc(ps->arr, newcapacity * sizeof(SLDataTyep));
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
我们这里用的时realloc来申请空间。这里的realloc具有增容的效果,时按倍数来增容的。所以用realloca比较好一点。
所以正确的尾插代码就是:
void SLPushBank(SL* ps,SLDataTyep x)
{
assert(ps);
newcapacity(ps);
ps->arr[ps->size]=x;
++ps->size;
}
大家有没有发现他其实就是调用了一次申请空间的函数?是的,就是这样的。
2.头插
我们可以从这张图可以了解到头插是怎样完成
void SLPushFront(SL* ps, SLDataTyep x)
{
assert(ps);
newcapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];//arr[1]=arr[0]
}
ps->arr[0] = x;
ps->size++;
}
但是这里要注意呀,千万不要把图中的第4步当成第1步哦。因为这样是不行,如果这样的话,那就会导致数据的丢失哦,因为这样的话第一个数据会替代第二个数据的哦
3.在指定位置之前插入数据
怎样可以实现在指定的位置之前插入数据呢?我们先来画图理解一下:
可以看出我们这里引入了一个新的变量pos,他的表达的是数组的下标位置 。由图我们可以看出我们在指定位置的下标处插入一个x,那么在pos到size之间的数据都要往后面移动一个单位。这里不要忘记szie要自加哦。
我们来看看代码的实现:
void SLInsert(SL* ps, int pos, SLDataTyep x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLnewcapacity(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];//ps->arr[pos+1]=ps->arr[pos]
}
ps->arr[pos] = x;
++ps->size;
}
这里我们需要注意些什么呢?1:注意pos的范围取值。2:size要自加。
四 、顺序表的删除
顺序表的 删除呢其实也有三种:尾删,头删,删除指定位置数据
1.尾删
我们先来看看图:
这里szie从1到2的过程中,就直接把数据4给删除了当然我们这里可以给他赋值-1,也可以删掉数据的,只不过用第一种方法要简便一点。我们来看代码:
void SLPopBank(SL* ps)
{
assert(ps);
assert(ps->size);
//ps->arr[ps->size - 1] = -1;
--ps->size;
}
2.头删
这里跟头插有点像,只不过这里是除了第一个数据之外,其他数据全部都向前移动一个单位。
void SLPopFront(SL* ps)
{
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size;
}
那为什么这里的 i < ps->szie-1呢?其实从图中我们可以看出来,因为他实质是从arr[ps->size-1]开始的。所以这里是szie-1.
3.删除指定位置数据
我们在解决代码问题的时候可以尝试画图解决,这样会比你直接想效率更高。而且也锻炼了你的动手能力。
我们现在来看看图像:
从图中可以看出我们想删除下标为2的数据(3)。只需要把4和5向前移动一步就ok了。这点需要注意的是 :是4向前移动,而不是5哦。我们来看看如何实现代码:
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size;
}
我们这里的i为什么要小于ps->size-1.那是因为ps->arr[size-1]是数组的最后一个数据,那么他要给给前面一个数据。意思就是说ps->arr [ps->size -2]=ps->arr [ps->size -1],那我们的i最后一次就是size-2,所以这里是size-1.
五 、查找数据
其实查找数据很简单,就一个循环就可以搞定了。
void SLFind(SL* ps, SLDataTyep x)
{
assert(ps);
for (int i = 0; i < ps->size ; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;//-1表示无效的下标
}
六、顺序表的整体代码展现与运行效果
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataTyep;
typedef struct SqList
{
SLDataTyep* arr;
int size;//有效数据
int capacity;//空间容量
}SL;
//typedef struct SqList SL;这样命名与结构体后的SL一样
void SLIntit(SL* ps)//初始化
{
assert(ps);
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
void SLDestory(SL* ps)//顺序表的销毁
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
void SLnewcapacity(SL* ps)//申请空间
{
if (ps->capacity == ps->size)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataTyep* tmp = (SLDataTyep*)realloc(ps->arr, newcapacity * sizeof(SLDataTyep));
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
void SLPushBank(SL* ps,SLDataTyep x)//尾插
{
assert(ps);
SLnewcapacity(ps);
ps->arr[ps->size] = x;
++ps->size;
/*ps->arr[ps->size]=x;
++ps->size;*/
}
void SLPushFront(SL* ps, SLDataTyep x)//头插
{
assert(ps);
SLnewcapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];//arr[1]=arr[0]
}
ps->arr[0] = x;
ps->size++;
}
void SLPopBank(SL* ps)//尾删
{
assert(ps);
assert(ps->size);
//ps->arr[ps->size - 1] = -1;
--ps->size;
}
void SLPopFront(SL* ps)//头删
{
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size;
}
void SLPrint(SL s)//打印
{
for (int i = 0; i < s.size; i++)
{
printf("%d ", s.arr[i]);
}
}
void SLInsert(SL* ps, int pos, SLDataTyep x)//在指定位置插入数据
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLnewcapacity(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
++ps->size;
}
void SLErase(SL* ps, int pos)//在指定位置之前删除数据
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size;
}
int SLFind(SL* ps, SLDataTyep x)//查找数据
{
assert(ps);
for (int i = 0; i < ps->size ; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;//-1表示无效的下标
}
int main()
{
SL sl;
SLIntit(&sl);
printf("尾插\n");
SLPushBank(&sl, 1);
SLPushBank(&sl, 2);
SLPushBank(&sl, 3);
SLPushBank(&sl, 4);
SLPrint(sl);
printf("\n");
printf("头插\n");
SLPushFront(&sl, 9);
SLPushFront(&sl, 8);
SLPrint(sl);
printf("\n");
printf("指定位置之前插入数据:\n");
SLInsert(&sl, 2, 99);
SLPrint(sl);
printf("\n");
printf("在指定位置删除数据:\n");
SLErase(&sl, 2);
SLPrint(sl);
printf("\n");
printf("尾删\n");
SLPopBank(&sl);
SLPopBank(&sl);
SLPrint(sl);
printf("\n");
printf("头删\n");
SLPopFront(&sl);
SLPopFront(&sl);
SLPrint(sl);
printf("\n");
int ret = SLFind(&sl, 2);
if (ret)
{
printf("找到啦,下标是%d\n", ret);
}
else
{
printf("没有找到\n");
}
SLDestory(&sl);
return 0;
}
这是代码的运行结果:
大家这里可以看见我们有些代码的函数里面有一些有assert(ps),其实这是用来判断ps是否为空的,如果为空就会报错。而还可以给你指出你出错的地方。用上断言呢,这样会使你的代码更健壮。
总结
我们这里不光要学习顺序表的相关知识,而且还要锻炼自己的画图水平,这样在你解决代码问题时可以给你带来很大帮助。今天就到这里把。要加油哟!各位。