目录
一、顺序表有什么功能?
二、实现顺序表的各个功能
1.前置准备
2.初始化顺序表
3.顺序表扩容
4.打印顺序表
5.增加顺序表成员
5.1尾增
5.2头增
6.删除顺序表中成员的内容
6.1尾删
6.2头删
7.查找成员
8.修改(替换)
9.插入(在目标位置插入成员)
10.定向删除(将目标位置的成员删除)
三、全部代码
1.函数头文件
2.函数实现
一、顺序表有什么功能?
这就像是大纲一样,只要我们明确了要实现的功能,之后就是努力地实现它们就好了,众所周知,顺序表是在计算机内存中以数组的形式保存我们需要的数据。既然数据被保存了,那么我们接下来就是对这些保存好的数据进行对应的操作,增删改查是必须要有的,接着,我们想将顺序表中的内容打印出来,看看到底有没有正确的存储我们需要的数据,我们可以再设计一个打印函数,想对顺序表的内容排序也可以再设计一个排序函数,反正根据自己所需设计,但是增删改查是最基本的,必须要有的。
二、实现顺序表的各个功能
1.前置准备
在实现顺序表的各个功能之前我们得先有点前置准备内容,顺序表的成员类型,顺序表这个结构体的设计,头文件的引用......这些操作笔者推荐都放在一个头文件中进行,这样在调用的时候仅需引用一个头文件即可完成我们需要的功能。
// SeqList.h
//将所需函数和所需头文件的引用放在一个头文件中,那么在使用的时候就只用引用一个头文件
#pragma once//防止头文件被重复引用
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>//可能要用到的头文件
typedef int SlDateType;
//将int typedef成SlDateType这样就可以区分int和顺序表成员
// 虽然它们的类型本质上都是int但是它们的字面含义已经不同
//当然了,可以把int换成别的类型
//这样子设计其实更多的是为了到时顺序表成员类型想更换直接从这换就全换了,不用一个一个换
typedef struct seqlist
{
SlDateType* a;
//创建一个指针类型的顺序表成员数据,为什么是指针?
//这样做是为了到时能够使用malloc和realloc对空间的大小进行开辟与修改
//相当于柔性数组
int sz;//已经存放了多少个成员
int capacity;//容量大小,以后判定空间是否够用可以通过这个来判定
}seqlist;//将结构体名字命名为seqlist,使用时更加方便
2.初始化顺序表
记得在书写代码之前将之前的顺序表头文件引用进来,这样就可以用到顺序表头文件的内容。
//顺序表.c
#include"顺序表.h"
void init_seqlist(seqlist* s1)//通过指针的形式访问,便能够对内容进行修改
{
s1->capacity = 3;//将容量大小初始化为3,为了更好地测试到时的扩容效果
s1->sz = 0;//将成员数量初始化为0,代表着此时没有存放成员
s1->a = (SlDateType*)malloc(sizeof(SlDateType) * s1->capacity);
//将顺序表的成员数组大小初始化和容量一样的大小
if (s1->a == NULL)//开辟失败的话直接退出程序
{
exit(-1);
}
}
在顺序表.c中书写完函数的内容后别忘了在顺序表.h中引用它,这样别人才能够只引用一个头文件就能够使用对应函数。
3.顺序表扩容
在存放顺序表成员之前我们应该要注意的一点就是,我们的容量是有限的,那么在触及容量的时候,也就是顺序表已经被装满的时候,我们应该要扩容处理,避免出现装不下内容的情况出现。
void if_enough(seqlist* s1)
{
if (s1->sz == s1->capacity)
//当容量和成员个数相当时,显然就已经存满了,需要扩容
{
s1->a = realloc(s1->a,sizeof(SlDateType)*s1->capacity*2);
//将容量扩大到原来容量的两倍
if (s1->a == NULL)
{
perror("if_enough");//错误提示
return;//中止程序
}
s1->capacity *= 2;//扩容成功,容量翻倍
printf("扩容成功,当前容量为%d\n",s1->capacity);//扩容成功给个提示
}
}
4.打印顺序表
在增加之前,我们就会意识到这样一个问题,倘若我增加了顺序表成员,但我又该如何分辨出我成功增加了顺序表成员呢,听着是不是很绕?其实就是我们不能够直接的看到顺序表的内容,因此我们可以使用打印的方式将顺序表的内容打印出来。
void print_seqlist(const seqlist* s1)
//将内容打印出来,但内容是不会被改变的,因此用const修饰,避免内容被修改
{
if (s1->sz == 0)
{
printf("顺序表为空,操作失败\n");
return;
}
int i = 0;
for (i = 0; i < s1->sz; i++)
{
printf("%d ", s1->a[i]);//将内容通过循环的方式打印出来
}
printf("\n");//打印完所有内容之后最好换行
}
5.增加顺序表成员
5.1尾增
什么是尾增?顾名思义,就是从最后面开始增加,即在顺序表的最后进行成员的添加,注意:在进行增加之前我们需要判断顺序表是否被放满,这个时候就可以使用我们之前创建的if_enough函数来判断是否需要进行扩容处理,如果需要则扩容,不需要则无事发生。
void seqlist_pushback(seqlist*s1,SlDateType x)
{
if_enough(s1);
s1->a[s1->sz] = x;//在顺序表的最后插入一个数据x
s1->sz++;
}
创建一个新的文件,测试一下我们之前写的代码
//test.c
#include"顺序表.h"
int main()
{
seqlist s1;
init_seqlist(&s1);//初始化顺序表
print_seqlist(&s1);
//将顺序表内容打印,但此时是空,故不能打印
seqlist_pushback(&s1, 1);//依次将1,2,3放入顺序表中
seqlist_pushback(&s1, 2);
seqlist_pushback(&s1, 3);
print_seqlist(&s1);//打印顺序表
seqlist_pushback(&s1, 2);//依次将2,3放入顺序表中
seqlist_pushback(&s1, 3);
print_seqlist(&s1);//打印顺序表
}
5.2头增
顾名思义,在顺序表的最开始进行成员的增加,我们不难想象,若是这个顺序表中已经有成员了这么办?难不成直接将这个成员替换成我们的目标?如果这样做就会少一个成员,根据数组的经验,我们只能够通过覆盖的方式先将所有的成员往后挪一个单位,再将最前面的成员替换成我们需要的成员。同样在开始之前我们应该要判断容量是否足够。
这里挪动是核心,同样也是一门学问,笔者在这画副图给大家,大家就懂得如何挪动了
由图可知,我们要先将最后面的成员往后挪动到下一个空间中,也就是sz对应的空间内容,得是sz-1的空间内容,sz-1的内容得是sz-2的内容,那么就可以通过循环的方式实现,sz-i指向的内容等于sz-i-1指向的内容,i实现一步步的覆盖,这里面比较难想的就是i的范围,由目标分析可知,当sz-i-1=0的时候结束循环,为什么?,因为当sz-i-1=0的时候,sz-i等于1,也就是1对应的目标,等于0对应的目标,完成这一步之后,所有的覆盖就已经结束,根据计算可知,i=sz-1,故i<sz便可以实现所有的覆盖。
void seqlist_pushfront(seqlist* s1, SlDateType x)
{
if_enough(s1);
int i = 0;
for (i=0;i<s1->sz;i++)//通过循环从后往前覆盖
{
s1->a[s1->sz - i] = s1->a[s1->sz-i-1];
}
s1->a[0] = x;//将首元素替换成x
s1->sz++;
}
同样可以测试一下
//test.c
#include"顺序表.h"
int main()
{
seqlist s1;
init_seqlist(&s1);//初始化顺序表
print_seqlist(&s1);
//将顺序表内容打印,但此时是空,故不能打印
seqlist_pushback(&s1, 1);//依次将1,2,3放入顺序表中
seqlist_pushback(&s1, 2);
seqlist_pushback(&s1, 3);
print_seqlist(&s1);//打印顺序表
seqlist_pushfront(&s1,520);//在最前面放入520
print_seqlist(&s1);//打印顺序表
}
6.删除顺序表中成员的内容
6.1尾删
这个就很简单了,我们只需要将此时顺序表的成员数量往回拨一位就行。为什么?举个例子,成员中已经有了1,2,3,4,5那么不难得出sz此时是5,指向的是5后面的空间,而当我们把数量往回拨的话,sz就指向了4,那么此时sz就指向了5对应的空间,下次你在再增加内容的时候这个空间就会被自动覆盖掉,同样,打印的话也是根据sz来打印的,会直接略过。唯一需要注意的一点就是,当顺序表的成员为空的时候,也就是没有成员的时候,强行删除的话就会造成越界问题的发生。因为没有成员的时候,sz为0,你对它强行进行删除,那么就会导致sz=-1,下一次再增加元素的时候,就会越界访问。
void seqlist_popback(seqlist* s1)
{
if (s1->sz == 0)
{
printf("顺序表为空,操作失败\n");
return;
}
s1->sz--;
}
6.2头删
头删相对于尾删麻烦一些,我们通过从前往后覆盖的方式将前面的内容覆盖成后面的内容
void seqlist_popfront(seqlist* s1)
{
if (s1->sz == 0)
{
printf("顺序表为空,操作失败\n");
return;
}
int i = 0;
for (i = 1; i < s1->sz; i++)
{
s1->a[i-1] = s1->a[i];//从前往后覆盖
}
s1->sz--;
}
同样,我们可以测试一下
7.查找成员
目标很简单,就是查找到目标成员然后打印出来,找不到就打印找不到,通过一次遍历就可以搞定
void seqlist_fine(const seqlist* s1,const SlDateType x)
//查找的目标是x,用const修饰是因为只是查找,不用修改
{
if (s1->sz == 0)
{
printf("顺序表为空,操作失败\n");
return;
}
int i = 0;
for (i = 0;i<s1->sz; i++)
{
if (s1->a[i] == x)//相等意味着找到了
{
printf("找到%d了,下标为%d\n",x,i);
return;
}
}
printf("目标不存在\n");
}
同样可以测试一下
8.修改(替换)
给对应的下标,和被修改的结果,对目标下标的内容进行修改,要注意的是,我们修改下标的范围只能是在成员组中进行修改,什么意思?就是说,我们不能对还没放成员的下标进行修改
void seqlist_modify(seqlist* s1, int pos, const SlDateType x)
{
if (pos >= s1->sz)
//当pos=sz时就已经是对还不是成员的空间进行操作了,更别说后面的了
{
printf("当前空间还不是成员的一部分,操作失败\n");
return;
}
s1->a[pos] = x;
}
同样可以测试一下
9.插入(在目标位置插入成员)
需要注意的便是,我们插入的范围是多少,很显然0~sz-1都是可以插入的,这里面已经是有内容的了,那么sz可不可以呢?可以,因为在sz这个位置插入就相当于尾插,还有一点需要注意,那便是插入成员,成员的数量是会增加的,那么也就是说,我们一样要在插入前判断是否需要扩容。
void seqlist_insert(seqlist* s1, int pos, const SlDateType x)
{
if_enough(s1);
if (pos > s1->sz)//比sz还大,意味着插入的位置会导致连续性被破坏
{
printf("插入位置出错,操作失败\n");
return;
}
int i = 0;
for (i = 0;i<s1->sz-pos; i++)
{
s1->a[s1->sz-i] = s1->a[s1->sz-i-1];//从后向前覆盖
}
s1->a[pos] = x;
s1->sz++;
}
测试一下
10.定向删除(将目标位置的成员删除)
有了之前插入的经验,这个就显得很简单,但要注意的一点则是,它的删除范围只能是0~sz-1,sz不被包括在内,因为sz显而易见还不是成员。
void seqlist_erase(seqlist* s1, int pos)
{
if (pos >= s1->sz)//等于sz时就已经是在删除未被定义的内容了
{
printf("删除位置出错,操作失败\n");
return;
}
int i = 0;
for (i=0;pos+i+1<s1->sz;i++)
{
s1->a[pos+i] = s1->a[pos+i+1];//从前向后覆盖
}
s1->sz--;
}
测试一下
当我们完成了插入和定向删除,其实之前的头插头删,尾插尾删都可以通过这两个函数来替换,这里就不再一一展示,升级版直接放在全部代码里面。文章来源:https://www.toymoban.com/news/detail-612317.html
三、全部代码
1.函数头文件
// SeqList.h
//将所需函数和所需头文件的引用放在一个头文件中,那么在使用的时候就只用引用一个头文件
#pragma once//防止头文件被重复引用
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>//可能要用到的头文件
typedef int SlDateType;
//将int typedef成SlDateType这样就可以区分int和顺序表成员
// 虽然它们的类型本质上都是int但是它们的字面含义已经不同
//当然了,可以把int换成别的类型
//这样子设计其实更多的是为了到时顺序表成员类型想更换直接从这换就全换了,不用一个一个换
typedef struct seqlist
{
SlDateType* a;
//创建一个指针类型的顺序表成员数据,为什么是指针?
//这样做是为了到时能够使用malloc和realloc对空间的大小进行开辟与修改
//相当于柔性数组
int sz;//已经存放了多少个成员
int capacity;//容量大小,以后判定空间是否够用可以通过这个来判定
}seqlist;//将结构体名字命名为seqlist,使用时更加方便
//初始化顺序表
void init_seqlist(seqlist*s1);
//打印顺序表
void print_seqlist(const seqlist* s1);
//尾增
void seqlist_pushback(seqlist* s1, SlDateType x);
//头增
void seqlist_pushfront(seqlist* s1, SlDateType x);
//尾删
void seqlist_popback(seqlist* s1);
//头删
void seqlist_popfront(seqlist* s1);
//查找成员
void seqlist_fine(const seqlist* s1,const SlDateType x);
//修改成员
void seqlist_modify(seqlist* s1, int pos, const SlDateType x);
//插入成员
void seqlist_insert(seqlist* s1, int pos, const SlDateType x);
//删除成员
void seqlist_erase(seqlist* s1, int pos);
2.函数实现
//顺序表.c
#include"顺序表.h"
void init_seqlist(seqlist* s1)//通过指针的形式访问,便能够对内容进行修改
{
s1->capacity = 3;//将容量大小初始化为3,为了更好地测试到时的扩容效果
s1->sz = 0;//将成员数量初始化为0,代表着此时没有存放成员
s1->a = (SlDateType*)malloc(sizeof(SlDateType) * s1->capacity);
//将顺序表的成员数组大小初始化和容量一样的大小
if (s1->a == NULL)//开辟失败的话直接退出程序
{
exit(-1);
}
}
void if_enough(seqlist* s1)
{
if (s1->sz == s1->capacity)
//当容量和成员个数相当时,显然就已经存满了,需要扩容
{
s1->a = realloc(s1->a,sizeof(SlDateType)*s1->capacity*2);
//将容量扩大到原来容量的两倍
if (s1->a == NULL)
{
perror("if_enough");//错误提示
return;//中止程序
}
s1->capacity *= 2;//扩容成功,容量翻倍
printf("扩容成功,当前容量为%d\n",s1->capacity);//扩容成功给个提示
}
}
void print_seqlist(const seqlist* s1)
//将内容打印出来,但内容是不会被改变的,因此用const修饰,避免内容被修改
{
if (s1->sz == 0)
{
printf("顺序表为空,操作失败\n");
return;
}
int i = 0;
for (i = 0; i < s1->sz; i++)
{
printf("%d ", s1->a[i]);//将内容通过循环的方式打印出来
}
printf("\n");//打印完所有内容之后最好换行
}
void seqlist_pushback(seqlist*s1,SlDateType x)
{
//if_enough(s1);
//s1->a[s1->sz] = x;//在顺序表的最后插入一个数据x
//s1->sz++;
seqlist_insert(s1,s1->sz,x);
}
void seqlist_pushfront(seqlist* s1, SlDateType x)
{
//if_enough(s1);
//int i = 0;
//for (i=0;i<s1->sz;i++)//通过循环从后往前覆盖
//{
// s1->a[s1->sz - i] = s1->a[s1->sz-i-1];
//}
//s1->a[0] = x;//将首元素替换成x
//s1->sz++;
seqlist_insert(s1,0, x);
}
void seqlist_popback(seqlist* s1)
{
/*if (s1->sz == 0)
{
printf("顺序表为空,操作失败\n");
return;
}
s1->sz--;*/
seqlist_erase(s1,s1->sz-1);
}
void seqlist_popfront(seqlist* s1)
{
//if (s1->sz == 0)
//{
// printf("顺序表为空,操作失败\n");
// return;
//}
//int i = 0;
//for (i = 1; i < s1->sz; i++)
//{
// s1->a[i-1] = s1->a[i];//从前往后覆盖
//}
//s1->sz--;
seqlist_erase(s1,0);
}
void seqlist_fine(const seqlist* s1,const SlDateType x)
//查找的目标是x,用const修饰是因为只是查找,不用修改
{
if (s1->sz == 0)
{
printf("顺序表为空,操作失败\n");
return;
}
int i = 0;
for (i = 0;i<s1->sz; i++)
{
if (s1->a[i] == x)//相等意味着找到了
{
printf("找到%d了,下标为%d\n",x,i);
return;
}
}
printf("目标不存在\n");
}
void seqlist_modify(seqlist* s1, int pos, const SlDateType x)
{
if (pos >= s1->sz)
//当pos=sz时就已经是对还不是成员的空间进行操作了,更别说后面的了
{
printf("当前空间还不是成员的一部分,操作失败\n");
return;
}
s1->a[pos] = x;
}
void seqlist_insert(seqlist* s1, int pos, const SlDateType x)
{
if_enough(s1);
if (pos > s1->sz)//比sz还大,意味着插入的位置会导致连续性被破坏
{
printf("插入位置出错,操作失败\n");
return;
}
int i = 0;
for (i = 0;i<s1->sz-pos; i++)
{
s1->a[s1->sz-i] = s1->a[s1->sz-i-1];//从后向前覆盖
}
s1->a[pos] = x;
s1->sz++;
}
void seqlist_erase(seqlist* s1, int pos)
{
if (pos >= s1->sz)//等于sz时就已经是在删除未被定义的内容了
{
printf("删除位置出错,操作失败\n");
return;
}
int i = 0;
for (i=0;pos+i+1<s1->sz;i++)
{
s1->a[pos+i] = s1->a[pos+i+1];//从前向后覆盖
}
s1->sz--;
}
好了,今天的分享到这里就结束了,感谢各位友友的来访,祝各位友友前程似锦O(∩_∩)O文章来源地址https://www.toymoban.com/news/detail-612317.html
到了这里,关于手把手教你怎么写顺序表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!