⭐在我们的数据结构当中,有很多存储数据的方式,我们的顺序表就是其中最简单的存储数据的方式之一。我们先来简单的介绍一下我们的顺序表。
⭐顺序表是线性表的一种,(线性表分为顺序表和链表)顺序表和我们的数组很相似,都是在内存当中开辟一块连续的空间进行数据的存储,但是对于我们的顺序表来说我们的数据必须是连续存储的。我们顺序表的实现大致思路和我们之前书写过的通讯录很类似,分为静态的顺序表和动态的顺序表两种。由于静态顺序表只能使用固定大小的空间,所以我们主要来实现更加灵活的动态顺序表。
⭐在我们编写程序之前最重要的是构建好思路,之后会给代码的编写带来很多好处。我们的顺序表的编写思路大致如下:
⭐首先我们可以先构建好一个结构体,我们可以通过这个结构体找到我们开辟好的空间,以及查看我们当前顺序表当中的元素的个数,以及顺序表的容量。之后再对我们开辟好的空间进行初始化。也可以说是将我们上一步开辟好的空间初始化,主要包括使用malloc函数开辟一块空间帮助我们顺序表存储数据,之后再将我们的顺序表中的元素个数定义为0,最后将我们顺序表的容量更改为我们malloc函数开辟好的内存的大小。示例如下:
⭐在这一步骤当中我们可以对我们定义的结构体进行调整,因为我们在构建顺序表的时候需要大量使用结构体的名字,所以我们可以将我们的结构体名重命名的简单一点。同时为了方便我们更改我们数组当中存储的数据类型我们同样可以使用宏定义,将我们的 int 定义为 DataType 到时候假如我们想要存储字符型的数据只需要更改一处即可。
⭐在我们完成上述步骤的时候我们需要做的就是思考我们的顺序表需要完成那些功能。通常情况下,我们的顺序表可以进行插入数据,插入功能包括头插,尾插,使用下标插入数据。也包括沃我们的删除的功能,需要我们完善头删,尾删,以及具体的查找删除的功能。(就像我们思维导图中所显示的那样)那么接下来我们就来一步一步实现我们上面提到的那些功能。
🌙1. 头插数据 PushFront
⭐我们的头插顾名思义也就是在我们的顺序表的第一个位置插入一个数据。假如我们的顺序表在不为空的情况下想要将在首位插入数据还不覆盖我们原有的元素就得将我们第一个位置的元素向后移动,但是这又会影响到我们之后的元素,所以我们就需要一步一步将我们的数据依次进行后移操作。
⭐就像是我们上面所进行的操作一样,为了不影响我们原有的数据所以我们需要从最后的数据开始进行移动,依次向前。移动完之后的效果如下:
⭐之后我们就可以将我们的数据插入第一个空位置当中。既然有了思路我们的代码实现起来就很简单了,关于头插函数的代码如下:
//头插数据元素
void SLPushFront(SL* s,int num)
{
assert(s);
//先判断容量是否已满,满就先扩容
check_capicity(s);
int i = 0;
int tmp = s->sz;
for (i = tmp; i > 0; i--)
{
s->pa[i] = s->pa[i-1];
}
s->pa[0] = num;
s->sz++;
}
⭐在这里需要我们注意的是我们需要注意对于顺序表容量的检查,如果顺序表已满我们就需要对于我们的顺序表使用realloc函数进行扩容操作。(扩容的步骤和我们通讯录当中的步骤相同)扩容函数的代码如下:
//检测容量的函数
void check_capicity(SL* s)
{
assert(s);
if (s->sz == s->capicity)
{
//进行增容操作
DataType* ptr = (DataType*)realloc(s->pa,sizeof(DataType) * 2 * (s->capicity));
if (ptr == NULL)
{
perror("realloc");
return;
}
s->pa = ptr;
s->capicity = 2 * (s->capicity);
return;
}
else
{
return;
}
}
⭐ 当我们这个功能在写完的时候肯定需要先进行验证,防止后面错误太多无从下手,但是我们只是构建好我们的头插函数却没有功能将我们的顺序表中的数据显示出来,所以我们这个时候还需要重新创建一个print函数用于将我们的数据打印出来,便于我们观察。对于我们的print函数只需要使用下标进行指定元素的打印即可。代码如下:
//打印顺序表
void PrintSL(SL* s)
{
assert(s);
int i = 0;
for (i = 0; i < s->sz; i++)
{
printf("%d ", s->pa[i]);
}
}
⭐有了打印函数之后不要着急,检查一下有没有什么遗漏的地方,我们的顺序表中的数组是使用malloc函数进行开辟的,在使用完毕之后我们需要将我们开辟出来的空间还给操作系统,所以我们还需要完成一个destory函数用于销毁我们开辟出来的空间。代码如下:
//销毁顺序表的函数
void DestorySL(SL* s)
{
assert(s);
free(s->pa);
s->pa = NULL;
s->capicity = 0;
s->sz = 0;
return;
}
⭐我们程序运行所需要的函数已经被我们大致构建出来了,接下来我们就来检测一下我们头插函数的效果:
⭐我们第一次向顺序表当中头插一个1,之后在头插入2,3,4,5。之后的打印效果应改为5,4,3,2,1。和我们的运行效果相同。那么我也就证明我们此函数的构建成功。
🌙2.头删数据 PopFront
⭐在头插功能之后需要我们构建的就是我们的头删操作。我们需要将我们顺序表头部的数据删除,有一个很方便的思路也就是将我们之后的数据统一向前移动,第二个元素覆盖第一个元素,一次向后推。最后将我们的顺序表当中的元素个数减1即可。代码如下:
//头删数据
void SLPopFront(SL* s)
{
assert(s);
assert(s->sz != 0);
int tmp = s->sz;
int i = 0;
for (i = 1; i < tmp; i++)
{
s->pa[i - 1] = s->pa[i];
}
s->sz--;
}
⭐我们同样进行检测一下我们的程序运行效果:
⭐我们接着刚才头插进入的五个数据进行检验,我们调用三次头删函数,也就是删去5,4,3剩下2和1。和我们的运行结果相同。
🌙3.尾插数据 PushFront
⭐在完成我们的头插和头删函数之后就可以实现我们的尾插函数了。其实相比于我们的头插和头删函数来说我们的尾插和尾删函数就要简单的多了。我们只需要在我们的下标为sz的位置上面进行数据的修改,需要注意的是在进行尾插之前需要先检查我们的顺序表的容量。具体的代码如下:
//顺序表的尾插函数
void SLPushBack(SL* s,DataType n)
{
assert(s);
//检测是否需要增容
check_capicity(s);
s->pa[s->sz] = n;
s->sz++;
return;
}
⭐检测我们程序的运行的效果:
⭐尾插1,2,3,4,5运行结果就是1,2,3,4,5和我们的预想是一样的。
🌙4.尾删数据 PopFront
⭐对于我们的尾删函数,我们可以直接将我们的顺序表中的数据数量减一即可,因为我们打印以及添加数据的时候是根据顺序表当中的元素个数进行操作的,只要元素个数减一之后我们重新操作就会将此处的数据覆盖。代码实现如下:
//删除数据
void SLPopBack(SL* s)
{
assert(s);
if (s->sz == 0)
{
return;
}
s->sz--;
}
⭐我们需要注意的是判断我们顺序表当中的元素的个数,不能小于0,因为小于0就会越界,导致程序失误。
⭐程序运行无误。
🌙5.查找数据 Index
⭐我们最熟悉的可能就是index操作了,我们需要从0开始向后进行遍历,一直到sz-1为止。如果相同我们就返回该处的下标。代码如下:
//在顺序表当中查找一个元素
//如果找到了就返回下标,没有找到返回-1
int SLIndex(SL* s,int num)
{
int i = 0;
for (i = 0; i < s->sz; i++)
{
if (s->pa[i] == num)
{
return i;
break;
}
}
return -1;
}
⭐查找顺序表中3所处的位置,如图所示3在顺序表当中的下标正好为2。程序运行正常。
🌙6.插入数据 Insert
⭐最后一个功能是我们的插入数据的功能,我们需要向函数中传入我们想要插入元素的位置,以及插入的元素的内容。对于插入函数和我们的头插中使用到的思想很相似,我们只需要找到下标为pos的元素,之后将此位置的数据以及之后的数据全部后移即可。代码如下:
//顺序表中插入数据
void SLInsert(SL* s, int pos, int num)
{
assert(s);
//判断顺序表当中是否有充足的数据保证顺序存储
assert(pos <= s->sz);
//判断容量
check_capicity(s);
int i = 0;
for (i =s->sz; i >pos; i--)
{
s->pa[i] = s->pa[i - 1];
}
s->pa[pos] = num;
s->sz++;
}
⭐就像我们上面验证的那样我们该部分的功能也可以正常的运行,那么到此我们顺序表的构建也就到此结束了。完整代码如下:
//test.c
#define _CRT_SECURE_NO_WARNINGS
#include"list.h"
SL s;
void test2()
{
SLPushFront(&s, 1);
SLPushFront(&s, 2);
SLPushFront(&s, 3);
SLPushFront(&s, 4);
SLPushFront(&s, 5);
SLPushFront(&s, 6);
SLPushFront(&s, 7);
SLPopFront(&s);
SLPopFront(&s);
PrintSL(&s);
}
void test1()
{
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
SLPushBack(&s, 5);
PrintSL(&s);
printf("\n");
SLInsert(&s, 3, 11);
SLInsert(&s, 1, 21);
SLInsert(&s, 0, 33);
SLInsert(&s, 0, 66);
PrintSL(&s);
}
void test3()
{
SLInsert(&s, 0, 1);
SLInsert(&s, 1, 2);
PrintSL(&s);
}
int main()
{
InitSL(&s);
test1();
DestorySL(&s);
return 0;
}
//list.c
#define _CRT_SECURE_NO_WARNINGS
#include"list.h"
//初始化顺序表
void InitSL(SL* s)
{
assert(s);
DataType* pf = (DataType*)malloc(sizeof(DataType) * MAX_NUM);
if (pf == NULL)
{
perror("malloc");
return;
}
s->pa = pf;
s->capicity = MAX_NUM;
s->sz = 0;
}
//销毁顺序表的函数
void DestorySL(SL* s)
{
assert(s);
free(s->pa);
s->pa = NULL;
s->capicity = 0;
s->sz = 0;
return;
}
//检测容量的函数
void check_capicity(SL* s)
{
assert(s);
if (s->sz == s->capicity)
{
//进行增容操作
DataType* ptr = (DataType*)realloc(s->pa,sizeof(DataType) * 2 * (s->capicity));
if (ptr == NULL)
{
perror("realloc");
return;
}
s->pa = ptr;
s->capicity = 2 * (s->capicity);
return;
}
else
{
return;
}
}
//顺序表的尾插函数
void SLPushBack(SL* s,DataType n)
{
assert(s);
//检测是否需要增容
check_capicity(s);
s->pa[s->sz] = n;
s->sz++;
return;
}
//打印顺序表
void PrintSL(SL* s)
{
assert(s);
int i = 0;
for (i = 0; i < s->sz; i++)
{
printf("%d ", s->pa[i]);
}
}
//删除数据
void SLPopBack(SL* s)
{
assert(s);
if (s->sz == 0)
{
return;
}
s->sz--;
}
//头插数据元素
void SLPushFront(SL* s,int num)
{
assert(s);
//先判断容量是否已满,满就先扩容
check_capicity(s);
int i = 0;
int tmp = s->sz;
for (i = tmp; i > 0; i--)
{
s->pa[i] = s->pa[i-1];
}
s->pa[0] = num;
s->sz++;
}
//头删数据
void SLPopFront(SL* s)
{
assert(s);
assert(s->sz != 0);
int tmp = s->sz;
int i = 0;
for (i = 1; i < tmp; i++)
{
s->pa[i - 1] = s->pa[i];
}
s->sz--;
}
//顺序表中插入数据
void SLInsert(SL* s, int pos, int num)
{
assert(s);
//判断顺序表当中是否有充足的数据保证顺序存储
assert(pos <= s->sz);
//判断容量
check_capicity(s);
int i = 0;
for (i =s->sz; i >pos; i--)
{
s->pa[i] = s->pa[i - 1];
}
s->pa[pos] = num;
s->sz++;
}
//在顺序表当中查找一个元素
//如果找到了就返回下标,没有找到返回-1
int SLIndex(SL* s,int num)
{
int i = 0;
for (i = 0; i < s->sz; i++)
{
if (s->pa[i] == num)
{
return i;
break;
}
}
return -1;
}
//list.h
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#define DataType int
#define MAX_NUM 4
//定义一个结构体
typedef struct SeqList
{
DataType* pa;
int sz;
int capicity;
}SL;
//初始化顺序表的函数
void InitSL(SL* s);
//销毁顺序表的函数
void DestorySL(SL* s);
//顺序表尾插函数
void SLPushBack(SL* s);
//打印顺序表
void PrintSL(SL* s);
//尾删数据
void SLPopBack(SL* s);
//头插数据
void SLPushFront(SL* s,int num);
//尾插数据
void SLPopFront(SL* s);
//在顺序表当中插入一个数据
void SLInsert(SL* s, int pos, int num);
//在顺序表当中查找一个数据
int SLIndex(SL* s,int num);
⭐那么我们本次博客的全部内容也就到此结束了,感谢您的观看。