👻个人主页: 起名字真南
目录
- [1 线性表](#1 线性表)
- [2 顺序表](#2 顺序表)
-
- [2.1 概念和结构](#2.1 概念和结构)
- [2.2 顺序表的实现](#2.2 顺序表的实现)
-
- [2.2.1 头文件的定义](#2.2.1 头文件的定义)
- [2.2.2 初始化](#2.2.2 初始化)
- [2.2.3 检查空间大小](#2.2.3 检查空间大小)
- [2.2.4 尾插](#2.2.4 尾插)
- [2.2.5 打印](#2.2.5 打印)
- [2.2.6 头插](#2.2.6 头插)
- [2.2.7 查找指定数据](#2.2.7 查找指定数据)
- [2.2.8 头删](#2.2.8 头删)
- [2.2.9 尾删](#2.2.9 尾删)
- 2倍
1 线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列,线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表,链表,栈,队列,字符串...
线性表在逻辑上是连续的,是一种线性结构但是在物理上(内存中存储)不一定是连续的。线性表在物理上存储时通常是以数组或者链表的形式技能型存储。
2 顺序表
2.1 概念和结构
顺序表使用一段物理地址连续的存储单元来进行储存数据的线性结构。一般情况下底层是数组。
1 静态顺序表:使用定长数组储存元素
在图片中N的实际大小为7使用define进行定义是为了方便后期数据的修改,其中arr指向的是数组首元素的地址,size指向的是最后一个元素的尾端实际大小为4但是在数组中以0为第一个下标是所以是尾端
2 动态顺序表:使用动态开辟的数组进行存储
动态顺序表比静态顺序表多了一个capacity用来记录目前所开辟空间的大小并且使用指针来指向动态开辟的数组而不是使用数组名。
2.2 顺序表的实现
静态顺序表只适用于确定的数据元素的个数,如果不确定空间开多了则会造成浪费,开少了不够用,所以我们一般使用动态顺序表,根据需求来开辟空间。
2.2.1 头文件的定义
c
typedef int SeqListDataType;
typedef struct SeqList
{
SeqListDataType* arr; // 指向动态开辟的数组
size_t size; //有效数据个数
size_t capacity; //开辟空间的大小
}SeqList;
2.2.2 初始化
关于顺序表的初始化有两种方法,第一种是直接给capacity初始化为一个任意的值,第二种则是初始化为0。
第一种初始化方法,在这里我们需要注意因为在初始的条件下我们是给定了空间的,所以这里的arr则不能为空必须开辟空间,在这里我们使用malloc来开辟空间。
c
void SeqListInit(SeqList* SL)
{
assert(SL);
SL->capacity = 4;
SL->size = 0;
SL->arr = (SeqListDataType*)malloc(SL->capacity * sizeof(SeqListDataType));
}
第二种方法则是初始化为0
c
void SeqListInit(SeqList* SL)
{
assert(SL);
SL->capacity = 0;
SL->size = 0;
SL->arr = NULL;
}
2.2.3 检查空间大小
我们不管是进行头插还是尾插都需要检查开辟的空间是否能够满足当前的需求,所以在进行对数据的修改的时候需要判断空间是否足够?
c
void SeqListCheck(SeqList* SL)
{
assert(SL);
//检查数据和空间的大小
if (SL->capacity == SL->size)
{
//三目表达式
size_t newcapacity = SL->capacity == 0 ? 4 : 2 * SL->capacity;
SeqListDataType* tmp = (SeqListDataType*)realloc(SL->arr, newcapacity * sizeof(SeqListDataType));
if (tmp == NULL)
{
perror("realloc:");
exit(1); //直接退出程序不再执行
}
SL->capacity = newcapacity;
SL->arr = tmp;
}
}
这个检查空间大小是在空间初始化为0的时候的代码 ,我们运用到了一个三目操作符来判断空间是否为空,判断的目的是因为if的循环条件是当空间大小等于数据个数来进行循环,但是满足这种条件有两种情况,第一种就是顺序表为空的情况空间大小和数据个数都是0,第二种就是这里面的数据已经满了需要开辟空间,如果我们开辟了空间的话需要修改的数据是两个,一个是空间大小,另一个是将动态开辟的空间给arr。
注意我们在开辟空间的时候最好创建一个临时的变量用来接收,因为一旦如果开辟失败realloc则会返回NULL赋给我们的arr这样就会造成数据的丢失,为了避免这种情况我们先创建一个临时变量并判断是否开辟成功,如果不为空则将tmp赋值给arr。如果初始化的时候是直接赋值那么这里的三目操作符可以省略,直接SL->capacity = 2 * SL->capacity就可以了。
2.2.4 尾插
在定义中定义的数组中元素的个数size不仅可以作为记录顺序表中数据的多少同时也可以作为指向顺序表最后一个元素的下一个地址的指针,
所以我们在进行尾插数据的时候直接在size位置插入即可。注意我们每次进行插入数据的时候都要判断是否为空并且检查空间的小。因为我们需要对顺序表进行修改所以需要传指针(*),结尾记得size++
c
void SLPushBack(SeqList* SL, SeqListDataType data)
{
assert(SL);
SeqListCheck(SL);
SL->arr[SL->size] = data;
SL->size++;
}
2.2.5 打印
因为我们有size用来记录数组中元素的大小所以只需要一个for循环遍历即可
c
void SLPrint(SeqList SL)
{
int i = 0;
for ( i ; i < SL.size; i++)
{
printf("%d ", SL.arr[i]);
}
printf("\n");
}
2.2.6 头插
顺序表的头插想要执行这个操作就需要将顺序表中的所有数据向后移动一个位置然后将数据头插在下标为0的地点。
为了保护数据在移动的时候不会出错所以我们直接从size-1的位置(最后一个元素的地址)开始向size(最后一个元素的下一个地址)移动就不会丢失数据。如果从第一个开始向后移动则会覆盖原有的数据在造成数据的丢失。
c
void SLPushFront(SeqList* SL, SeqListDataType data)
{
assert(SL);
SeqListCheck(SL);
for (int i = SL->size; i > 0; i--)
{
SL->arr[i] = SL->arr[i - 1];
}
SL->arr[0] = data;
SL->size++;
}
2.2.7 查找指定数据
在查找数据的时候可以不传指针但是为了代码的一致性和美观所以这里传的是指针,
c
size_t SLFind(SeqList* SL, SeqListDataType data)
{
assert(SL);
for (int i = 0; i < SL->size; i++)
{
if (SL->arr[i] == data)
{
return i;
}
}
printf("没有找到%d", data);
}
2.2.8 头删
进行头删的时候我们只需从第二个数据开始向前移动一位覆盖掉第一位的数据即可,需要注意的是循环条件是 i < SL->size - 1,而不是size,举一个简单的例子,如图所示包含四个数据的顺序表只需要执行三次即可。
c
void SLPopFront(SeqList* SL)
{
assert(SL);
for (int i = 0; i < SL->size - 1; i++)
{
SL->arr[i] = SL->arr[i + 1];
}
SL->size--;
}
2.2.9 尾删
c
void SLPopBack(SeqList* SL, SeqListDataType data)
{
assert(SL);
assert(SL->size > 0);
SL->size--;
}
2倍
关于为什么每次开辟的空间是原空间的2倍的问题,这是个数学问题如果感兴趣的可以去网上搜索一下相关证明(我不说的因为是我不会)。