目录
前提须知:
数据结构:
什么是数据结构?
数据结构特点:
为什么需要数据结构:
顺序表:
线性表:
与数组区别:
静态顺序表与动态顺序表:
二者之间的区别:
编译器的实现:(以下使用头文件和源文件的书写方式)
初始化:
头文件:
源文件:
主函数部分:
销毁开辟空间:
头文件:
源文件:
主函数部分:
尾插:(在上述代码的基础上进行尾插操作)
尾插要考虑的因素:
头文件:
回答问题:
1)空间是否足够:
2)空间足够如何进行尾插:
如何扩容?
怎么扩容?
那如何申请空间呢?
注意事项:
主函数部分:
头插:(在上诉代码的基础上进行尾插操作)
头插的所需要进行思考的问题:
头文件:
回答问题:
怎么将原先的数据往后移,腾出第一个空间的位置:
主函数部分:
尾删:(在上诉的基础上进行尾删操作)
头文件:
需要注意的问题:
主函数部分:
头删:(在上诉代码的基础上进行头删)
头文件:
怎么删呢?
注意的问题:
源文件:
编辑 主函数部分:
编辑
在任意位置插入数据:
头文件:
图示:编辑
源文件:
插入部分:
完整版:
主函数部分:
编辑
删除指定位置的数据:
头文件:
源文件:
主函数部分:
编辑
编辑
查找指定的数据是否存在:
头文件:
源文件:
主函数部分:
编辑
编辑
使用打印顺序表的函数调用:
头文件:
源文件:
主函数部分:
总结:
答:数据结构是计算机存储、组织数据的⽅式,举例:数组是最简单的一种数据结构。
在顺序表之前,我们要先明白什么是线性表。
线性表:
- 线性表是一种总称, 指的是具有相同特性的一类数据结构的统称。
线性表特点:
- 线性表在逻辑上是线性结构,也就说是连续的一条直线。
- 但是在物理结构上并不一定是连续的线性表在物理上存储时,通常以数组和链式结构的形式存储。
- 逻辑结构:人为想象的。
- 物理结构:内存存储上,不一定是连续的,连续的举例是数组,数组地址是连续的。
而顺序表是线性表的一种,相当于苹果是水果的一种,且因为顺序表的底层结构是数组,所以顺序表在逻辑结构上是线性的,在物理结构上也是线性的。
先前讲诉过,数组是同种类型数据的集合。
而且数组分为两种,定长数组和动态数组,定长数组是一开始就设定了数组大小,动态数组是数组的大小不确定,详情看柔性数组。
柔性数组_明 日 香的博客-CSDN博客
而顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口,这是顺序表与数组的区别。
同时也可以通过数组的分类将顺序表分为两种,静态顺序表和动态顺序表。
静态顺序表和动态顺序表的声明结构,其实顺序表的声明结构就是一种结构体。
结构体的简单介绍(1)_明 日 香的博客-CSDN博客
//静态顺序表
struct SeqList
{
int a[100];//定长数组
int size; //有效个数
}
- int a[100]表示这个顺序表的大小是100个int类型的字节数大小,也可以说是400个字节大小。
- 而int size 表明了该顺序表中放入了多少有效的数据。
- struct是结构体的关键字,表明了顺序表的声明结构其实就是结构体。
//动态顺序表
struct SeqList
{
int* a;
int size;//有效数据的个数
int capacity;//空间大小
}
- int*a 指向的是一个数组空间的首个元素地址,间接表明了顺序表的底层结构是数组。
- int capacity 指向的是开辟空间的大小,也就是顺序表空间的大小,因为是动态的,所以需要开辟空间。
- 在使用的过程中,可以将a直接当成数组名使用,这是允许的。
静态顺序表缺陷:空间给少了不够⽤,给多了造成空间浪费 。
注意:顺序表是一种声明,所以应该在头文件中输写。
以动态顺序表为例子。
在图中,typedef 将int 类型 和 顺序表 进行了重新命名。
int 类型 定义命名的意义是为了方便以后进行类型的更改,这是顺序表和数组的区别之一。
而顺序表 struct SeqList重命名为SL 为了以后方便书写。
创建一个void 类型的函数SLInit 该函数的参数是SL*类型的,形参是ps
结构体的赋值,详情:结构体的简单介绍(1)_明 日 香的博客-CSDN博客
将顺序表的空间大小和有效数字初始化为0,将顺序表的指针指向NULL
因为局部变量的关系,以及传值调用中,形参的改变不会影响实参的因素,所以我们这里使用传址调用。
C语言 实参与形参 传值调用与传址调用-CSDN博客
销毁开辟的空间,在销毁之前我们必须判断这个空间是否存在,或者说是否开辟,如果存在,则需要使用free释放空间,free的本质是将开辟的空间交还给内存。
free详情:malloc与free_明 日 香的博客-CSDN博客
在尾部插入数据。
如图所示,在下标为6的位置上插入数据,这种就是尾插。
x表示插入的数据,SL*ps是插入空间的地址
在动态顺序表中,capacity是表示顺序表的空间的大小,size是表示顺序表中的有效数据的个数,所以当capacity==size的时候,就可以说明顺序表的空间是否满了。
使用if进行判断二者是否相等,如若相等,准备进行空间的扩容,若不相等则直接进行尾插。
因为空间足够使用,所以在顺序表这个数组中插入数据即可。
且,因为size无论在何时都表明了数组的大小,而size-1表明了当前顺序表这个数组的最大下标,所以在顺序表这个数组中,若要插入数据,直接在size这个位置中插入数据即可。
当然,在最后别忘了将 有效数据个数+1,这是为了进行多次的尾插所必要的代码,且直接插入是基于capacity 够的情况下,所以capacity不需要进行改动。
如何扩容?怎么扩容?需要扩大多少空间合适?
图中的代码类比,realloc函数的调整空间大小,基于realloc函数的特性,如若调整空间大小失败,那么这个空间则会丢失,所以直接赋予ps->a有可能造成原空间地址失效,所以需要使用tmp进行中间赋值,然后判断,判断成功后在赋予ps->a
扩容:
- ps->a表示需要扩容空间的起始地址
- ps->capacity*2*sizeof(SLDataType),前者表示当前的空间大小,sizeof表示计算当前顺序表的字节大小,*2表示我们的扩容方法。
- (需要注意的是,空间大小是有类型的,所以空间大小不能代表字节数的多少)
注意:
当扩容成功后,记得地址的转移以及当前空间需要扩大到原来的两倍,以便下次扩容的时候不会出错。
有些人会将capacity 初始化为0,导致后面的代码出错,所以这里新建立一个代码进行判断。
这是一个三目表达式,使用该表达式进行判断空间大小是否为0,如果是0则赋予初值4,如果不是0则将空间进行扩容。
也因此,其余部分也需要进行整改。
完整的代码是:当空间不够后进行扩容,扩容后空间足够,进行尾插操作
头插,在头部插入数据,但是放在数组中,就相当于将原先的首元素变为第二个元素,原先的第二个元素变成第三个元素······在不覆盖其他元素的基础上腾出第一个空间的位置。
因为尾删不用管空间是否足够,所以我们需要删除最后一个数据就行了。
也因为不用管空间大小,所以直接进行有效数据的个数-1使得最后尾部的数据失效即可。
注意在尾删的同时要注意顺序表是否为空,我们需要进行判断,判断是否为空要判断ps->a==NULL是否成立,成立就为空的地址。
在这里我们使用断言进行判断预警
因为必须把第一个数据删了,然后之后的数据补上,所以可以直接覆盖,将第一个位置的数据用第二个位置的数据直接覆盖,而后,第二个位置的数据被第三个位置的数据覆盖,第三个位置的数据被第四个位置的数据覆盖,以此类推············
在此之前判断数据空间是否为空。
为何是i
size-1呢因为要把p->size-2的数据被p->size-1的数据覆盖,且因为p->size-1的数据因为往前挪,且因为头删,有效数据会减少,所以挪动后,在顺序表这个数组中下标是p->size-1的位置的数据应该是空的,然整体的有效数据个数-1,size-1变成了size
SL*ps是顺序表这个空间的地址,int pos是顺序表这个数组的下标位置,x是指插入的数据
以上图举例:要把原来下标1的数据往后移动,这个和头插有些类似,要把下标为1的数据和它之后的数据都往后移动,让出下标1的位址空间。
注意事项:还是得先查看空间是否足够,不够进行空间的扩展,空间扩展参考尾插。
删除指定位置的数据,和头删有些类似,直接将指定的位置,被指定位置的后一位进行覆盖,然后后一位被后两位覆盖
注意事项:需要进行空间是否为空的判断。
注意事项:先判断空间是否为空 ,且bool类型是只能返回true和flase的。
- 顺序表的声明结构其实是一种结构体
- 顺序表的底层结构是数组,所以更多的时候我们可以将顺序表当作数组来进行使用
顺序表的缺点:
- 从之前的博客中,我们得知,顺序表的本质实际上是一种数组。
- 而数组的最大特征就是连续的空间。
- 也因此,在线性表中,顺序表是一种物理上和逻辑上都连续的直线。