【C语言】用纯C来创建顺序表
admin
2024-04-04 11:25:16
0

🐖前言

⭐什么是顺序表?
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
其实顺序表就是数组,要求数据连续存储的

🐖顺序表的创建

需要创建一个小项目工程
创建三个文件
seqlist.h放顺序表的头文件,函数声明
seqlist.c放顺序表的函数
test.c是主函数。存放框架

(1)🐕创建一个静态顺序表

对于这个来说,顺序表就是一个数组,但是我们需要记录顺序表的有效数据,所以我们就用结构体创建一个顺序表,为了让后续用顺序表的时候更加的方便,我们可以typedef一下让我的代码看起来更加简洁。

#define N 10
//静态顺序表
typedef struct Seqlist
{int a[N];int size;//记录存储多少个有效数据
}SL;//重新命名一下让后面的操作更加方便

但是呢我们再用的时候是不是只能用int型的数据了,所以我们可以将数据类型直接typedef改个名字,当我们想改数据类型的时候就可以直接再typedef上改。

#define N 10//将数据类型重定义一下
typedef int DataType;
//静态顺序表
typedef struct Seqlist
{DataType a[N];int size;//记录存储多少个有效数据
}SL;//重新命名一下让后面的操作更加方便

但是呢对于静态的顺序表,他是不是有点太死板了,我们开辟多了空间,用不完怎么办,开辟少了空间,不够用这么办这都是需要我们考虑的东西。所以开一个动态的就好了呀。

(1.2)🐕创建一个动态的顺序表

为了合理安排我们的需求,我们就可以动态开辟空间,我们需要多少空间就开辟多少空间,请看下面的代码

//动态顺序表---按照需求扩空间
typedef struct Seqlist
{DataType *a;//动态开辟数组的指针int size;//记录存储多少个有效数据int capacity;//空间容量,当sz大于他的时候再扩容
}SL;//重新命名一下让后面的操作更加方便

(2)🐕初始化顺序表

将数据都设为0,并且这是用指针的,这样才能改变原数据。

//初始化顺序表将数据都设为0
void SLInit(SL* ps)
{ps->a = NULL;ps->sz = 0;ps->capacity = 0;
}

(3)🐕销毁顺序表

再用动态内存开辟玩空间,我们需要销毁开辟的空间,将其free掉,为什们要现在写呢 ,就是为了一会测试我们写的函数的时候,不会报错。并且顺便把顺序表中的sz,capacity置为0.

//销毁顺序表
void SLDistory(SL* ps)
{//防止他是空指针if (ps->a != NULL){free(ps->a);ps->a = NULL;ps->capacity = 0;ps->sz = 0;}}

(4)🐕顺序表的尾插

如果你认为就是再顺序表的结尾插入数据,那你就想简单了,我们在上面还没有动态开辟空间呢,并且如果当你开辟的空间用完了,怎么办,这都是我们需要考虑的

我们这样想,顺序表是空的,我们刚开始肯定要插入空间,所以我们可以在顺序表的尾插中开辟空间,就不用再初始化的时候开辟空间,并且我们通过C语言中动态内存的知识,我们知道realloc当他用NULL的时候,功能相当于malloc,所以我们可以利用这个特点来写。
其他细节就看代码吧。

//顺序表的尾插
void SLPushBack(SL* ps,DataType n)
{//当空间不够的时候扩容if (ps->sz == ps->capacity){//这时候就将capacity改变为新的大小//但是刚开始的时候capacity等于0,就算×2他还是0.//所以我们用一个三目表达式int newcapacity = ps->capacity == 0 ? 4: ps->capacity * 2;//当我知道开辟多少空间我们就可以用realloc//刚开始ps->a,是空指针,所以他这时就相当于mallocSL* tmp = (SL*)realloc(ps->a, newcapacity * sizeof(DataType));//防止realloc开辟一个空指针。if (tmp == NULL){perror("realloc");exit(-1);}//将开辟空间的指针传给ps->aps->a = tmp;//将容量也改变ps->capacity = newcapacity;}ps->a[ps->sz] = n;ps->sz++;
}

(4)🐕打印顺序表

该操作就是简单打印一下,我们每写一个部分,我们就检查一部分,打印就是为了看我们写的最后的效果达到了没有,这个非常简单,大家直接看代码。
⭐对于这个部分,我们需要提醒一下大家,不要等我们将全部代码写完在进行调试,我们写一个部分就测试一部分,看看这个部分有没有问题,没有问题我们再进行下一部分。

//打印顺序表
void SLPrint(SL* ps)
{int i = 0;for (i = 0; i < ps-> sz; i++){printf("%d ", ps->a[i]);}
}

(5)🐕尾删

有同学可能想能不能直接将最后一个数据直接变成0,是不是将相当于将最后一个数据删除了呢
但是同学们可以这也想,本来人家原本的数据就是0,那你再改成0,好像什么都没做,
其实可以直接将sz–,不就巧妙的将最后一个数据删掉了吗,有的人想不明白的,他明明还在哪呀,怎么叫删掉了呢,其实你sz–,你不用他了,下一回插入的时候不就直接覆盖掉了吗?

//尾删顺序表
void SLPopBack(SL* ps)
{//检查顺序表是否为空//温柔的检查/*if (ps->sz == 0){printf("顺序表为空\n");return;}*///暴力的检查//断言看他是否空了assert(ps->sz > 0);//将sz的值直接减一就相当于删除了ps->sz--;
}

(6)🐕头插

我们在想一下,在进行头插的时候我们需要从第一个位置开始依次向后移一个位置,如果我们从sz=1的时候向后移。我们会将后面的东西覆盖掉,说明从前到后是不可取的,我们应该从sz最大的时候向后移动一位,再将前面的依次向后退一个。

我们还需要注意一点,我们再插入的时候还需要检查一下,顺序表的容量是不是满了,因为我们再尾插中用过这个,所以我们可以将这个封装一个检查容量函数

(7)🐕检查容量函数

: 代码如下

//检查顺序表容量
void SLCheckCapacity(SL* ps)
{assert(ps);//当空间不够的时候扩容if (ps->sz == ps->capacity){//这时候就将capacity改变为新的大小//但是刚开始的时候capacity等于0,就算×2他还是0.//所以我们用一个三目表达式int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//当我知道开辟多少空间我们就可以用realloc//刚开始ps->a,是空指针,所以他这时就相当于mallocSL* tmp = (SL*)realloc(ps->a, newcapacity * sizeof(SLDataType));//防止realloc开辟一个空指针。if (tmp == NULL){perror("realloc");exit(-1);}//将开辟空间的指针传给ps->aps->a = tmp;//将容量也改变ps->capacity = newcapacity;}
}
头插函数
代码
//头插顺序表
void SLPushFront(SL* ps, SLDataType n)
{//检查容量是否满了SLCheckCapacity(ps);int i = 0;//下面这个大家画图自己看看,肯定能弄出来int end = ps->size - 1;while (end >= 0){ps->a[end + 1] = ps->a[end];end--;}ps->a[0] = n;ps->sz++;
}
在这里提一个问题
头插和尾插的时间复杂度是多少
我们可以清楚的看到尾插的时间复杂度是O(1),直接动最后一个就行了
而头插需要从最后一个开始挪,所以他的时间复杂度是O(N).
所以我们再用的时候一般都用尾插

(8)🐕头删

头删和尾删基本相同,最关键的问题就是越界问题,
我们需要知道,越界不一定会报错,但是后续的操作一定会受影响
越界读,不一定会报错。
越界写,可能会被检查出来,他是一种抽查。不会一个一个查。

//头删顺序表
void SLPopFront(SL* ps)
{assert(ps);int i = 0;//他删除可能会越界,因为删除次数大于sz的时候assert(ps->sz > 0);//画图确认逻辑很简单的int begin = 1;while (begin < ps->sz){ps->a[begin - 1] = ps->a[begin];begin++;}ps->sz--;
}

(9)🐕从中间插入数据

我们想一个问题,对于我们这从中间插入的数据,是不是包括头插和尾插呀,直接将参数改一下是不是能轻松的,所以我们头插尾插可以直接调用这个函数,就变得很简单。

//中间的插入
//在pos位置插入数据n
void SLInsert(SL* ps, int pos, SLDataType n)
{assert(ps);//pos也需要检查一下,防止pos不在范围内assert(pos >= 0);assert(pos <= ps->sz);//挪动数据//先检查容量SLCheckCapacity(ps);int end = ps->sz - 1;while (end >= pos){ps->a[end + 1] = ps->a[end];end--;}ps->a[pos] = n;ps->sz++;
}

(10)🐕从中间删除数据

我们想一个问题,对于我们这从中间删除的数据,是不是包括头删和尾删呀,直接将参数改一下是不是能轻松的,所以我们头删尾删可以直接调用这个函数,就变得很简单。

//删除pos位置的数据
void SLErase(SL* ps, int pos)
{assert(ps);assert(pos >= 0);assert(pos < ps->sz);//挪动数据覆盖int begin = pos + 1;while (begin < ps->sz){ps->a[begin - 1] = ps->a[begin];begin++;}ps->sz--;
}

(11)🐕查找数据

我们如果想通过找到数据,然后再做删除插入操作,那么我们需要在封装一层函数,找到数据对应的下标,然后我在进行插入删除操作。

//查找数据的位置
int SLFind(SL* ps, SLDataType x)
{assert(ps);for (int i = 0; i < ps->sz; i++){if (ps->a[i] == x){return i ;}}return -1;
}

(12)写菜单

对于数据结构来说,我们的菜单其实就是起一个交互作用,对于我们学习知识来说没有太大的影响,并且对于调试我们每写一个功能来说也是不方便的,所以我们就简单做一个框架,大家把前面的东西弄明白就行。

#define _CRT_SECURE_NO_WARNINGS 1
#include"seqlist.h"
void menu()
{printf("**************************************\n");printf("****1.尾插数据   2.尾删数据      *****\n");printf("****3.头插数据   4 头删数据      *****\n");printf("****5.插入数据   6.删除指定数据  *****\n");printf("****7.查找数据   8.打印数据      *****\n");printf("****0.退出                       *****\n");printf("**************************************\n");
}
int main()
{SL sl;SLInit(&sl);int option = 0;int tmp = 0;do {printf("请输入操作数\n");scanf("%d", &option);switch(option){case 1:printf("请输入尾插的数据以-1结束\n");while (tmp!=-1){scanf("%d", &tmp);}SLPushBack(&sl,tmp);break;case 2:break; case 3:break; case 4:break; case 5:break; case 6:break; case 7:break;case 8:printf("打印数据\n");SLPrint(&sl);break;case 0:printf("退出程序\n");break;default:printf("输入错误请重新输入\n");break;}menu();} while (option);SLDistory(&sl);return 0;
}

seqlist.c的内容

我们在这里存放,函数的内部是实现

#define _CRT_SECURE_NO_WARNINGS 1#include "seqlist.h"//初始化顺序表将数据都设为0
void SLInit(SL* ps)
{assert(ps);ps->a = NULL;ps->sz = 0;ps->capacity = 0;
}//销毁顺序表
void SLDistory(SL* ps)
{assert(ps);//防止他是空指针if (ps->a != NULL){free(ps->a);ps->a = NULL;ps->capacity = 0;ps->sz = 0;}}
//检查顺序表容量
void SLCheckCapacity(SL* ps)
{assert(ps);//当空间不够的时候扩容if (ps->sz == ps->capacity){//这时候就将capacity改变为新的大小//但是刚开始的时候capacity等于0,就算×2他还是0.//所以我们用一个三目表达式int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//当我知道开辟多少空间我们就可以用realloc//刚开始ps->a,是空指针,所以他这时就相当于mallocSL* tmp = (SL*)realloc(ps->a, newcapacity * sizeof(SLDataType));//防止realloc开辟一个空指针。if (tmp == NULL){perror("realloc");exit(-1);}//将开辟空间的指针传给ps->aps->a = tmp;//将容量也改变ps->capacity = newcapacity;}
}//顺序表的尾插
void SLPushBack(SL* ps,SLDataType n)
{assert(ps);//SLCheckCapacity(ps);//ps->a[ps->sz] = n;//ps->sz++;SLInsert(ps, ps->sz, n);
}
//打印顺序表
void SLPrint(SL* ps)
{assert(ps);int i = 0;for (i = 0; i < ps-> sz; i++){printf("%d ", ps->a[i]);}printf("\n");
}//尾删顺序表
void SLPopBack(SL* ps)
{//检查顺序表是否为空//温柔的检查/*if (ps->sz == 0){printf("顺序表为空\n");return;}*///暴力的检查//断言看他是否空了assert(ps->sz > 0);//将sz的值直接减一就相当于删除了/*ps->sz--;*/SLErase(ps, ps->sz-1);
}//头插顺序表
void SLPushFront(SL* ps, SLDataType n)
{assert(ps);//检查容量是否满了//SLCheckCapacity(ps);//int end = ps->size - 1;//while (end >= 0)//{//	ps->a[end + 1] = ps->a[end];//	end--;//}//ps->a[0] = n;//ps->sz++;SLInsert(ps, 0, n);
}
//头删顺序表
void SLPopFront(SL* ps)
{assert(ps);//int i = 0;他删除可能会越界,因为删除次数大于sz的时候//assert(ps->sz > 0);画图确认逻辑很简单的//int begin = 1;//while (begin < ps->sz)//{//	ps->a[begin - 1] = ps->a[begin];//	begin++;//}//ps->sz--;SLErase(ps, 1);
}//中间的插入
//在pos位置插入数据n
void SLInsert(SL* ps, int pos, SLDataType n)
{assert(ps);//pos也需要检查一下,防止pos不在范围内assert(pos >= 0);assert(pos <= ps->sz);//挪动数据//先检查容量SLCheckCapacity(ps);int end = ps->sz - 1;while (end >= pos){ps->a[end + 1] = ps->a[end];end--;}ps->a[pos] = n;ps->sz++;
}
//删除pos位置的数据
void SLErase(SL* ps, int pos)
{assert(ps);assert(pos >= 0);assert(pos < ps->sz);//挪动数据覆盖int begin = pos + 1;while (begin < ps->sz){ps->a[begin - 1] = ps->a[begin];begin++;}ps->sz--;
}//查找数据的位置
int SLFind(SL* ps, SLDataType x)
{assert(ps);for (int i = 0; i < ps->sz; i++){if (ps->a[i] == x){return i ;}}return -1;
}

seqlist,.h的内容

我们在这里放函数的声明

#pragma once
#include
#include
#include
#define N 10//将数据类型重定义一下
typedef int SLDataType;静态顺序表
// 
//typedef struct Seqlist
//{
//	DataType a[N];
//	int size;//记录存储多少个有效数据
//}SL;//重新命名一下让后面的操作更加方便//动态顺序表---按照需求扩空间
typedef struct Seqlist
{SLDataType *a;//动态开辟数组的指针int sz;//记录存储多少个有效数据int capacity;//空间容量,当sz大于他的时候再扩容
}SL;//重新命名一下让后面的操作更加方便//顺序表的初始化
void SLInit(SL* ps);
//销毁顺序表
void SLDistory(SL* ps);
//打印顺序表
void SLPrint(SL* ps);
//检查顺序表容量
void SLCheckCapacity(SL* ps);//尾插顺序表
void SLPushBack(SL* ps,SLDataType n);
//尾删顺序表
void SLPopBack(SL* ps);//头插顺序表
void SLPushFront(SL* ps, SLDataType n);
//头删顺序表
void SLPopFront(SL* ps);//中间的插入删除
//在pos位置插入数据n
void SLInsert(SL* ps, int pos, SLDataType n);
//在pos位置删除数据n,
void SLErase(SL* ps, int pos);//查找数据的位置
int SLFind(SL* ps, SLDataType n);

总结

毫不夸张的说,这个其实是数据结构中最简单的部分,大家只有将这个部分弄明白,后面的学习才会更加通顺,大家一起加油。

相关内容

热门资讯

像黄渤他大舅一样搞笑的 像黄渤他大舅一样搞笑的比如黄渤他大舅了还有葛优征婚说的经典台词等等他大舅是谁呀
森屿暖树时光恋人墒以光年深海爱... 森屿暖树时光恋人墒以光年深海爱人 的拼音森屿sen yu暖树nuan shu时光恋人shi guan...
十二星座对应剑灵哪些职业 十二星座对应剑灵哪些职业这样你也能扯到一快去
泡荔枝用什么酒最好呢?选择合适... 近年来,泡荔枝等水果酒的风潮越来越热,不仅能在家轻松制作美味的果酒,还能根据个人口味调整酒的风味。然...
分享3款适合小暑时节的手工甜品... 小暑至,盛夏始。随着气温节节攀升,人体易生内热,出现心烦口渴、食欲不振等症状。此时,一碗清甜解暑的甜...
“贵州省汤”火了!解暑又减脂,... 最近不少地方遭遇持续高温,不少网友表示,在厨房里待十几分钟,浑身都是汗。这种天气下,吃什么,成了很多...
轻盈细腻的澳洲甜点 巴甫洛娃(... 主料:鲜鸡蛋白12个、糖450克 配料:白醋、香草精、玉米淀粉各1汤匙 做法: 1.用打蛋器打发鸡蛋...
西游记中唐僧和猪八戒误喝子母河... 西游记中唐僧和猪八戒误喝子母河水腹痛难忍。泉主人如意真仙为何不给悟空落胎?手机中唐僧和猪八戒,巫师蝎...
学生一般毕业的话,可以第二次进... 学生一般毕业的话,可以第二次进入大学重读吗?首先是一般情况下是不可以的,除非你是准备提升学历考研考博...
小说是一种作家写的假的事什么意... 小说是一种作家写的假的事什么意思?作家写小说,是一个根据真人真事进行艺术加工的过程,并不是完全虚构的...
自称为行者是什么意思? 自称为行者是什么意思?“行者”指的是出家而未经过剃度的佛教徒。自称行者就是说他自己是修行佛道之人。那...
有哪些带不字的四字成语? 有哪些带不字的四字成语?不毛之地 不眠之夜 不明不白 不明真相 不谋而合 不谋私利 不...
赞美女老师的诗句有哪些? 赞美女老师的诗句有哪些? 春蚕到死丝方尽,蜡炬成灰泪始干.----- 李商隐为人性僻耽佳句,语不惊人...
做手工巧克力会花多少钱? 做手工巧克力会花多少钱?那个是跟据你做多大的巧克力来定价的就跟做巧克力蛋糕一样跟据重量算。当地物价不...
中国人为什么很喜欢羽生结弦?他... 中国人为什么很喜欢羽生结弦?他有何特别之处?因为羽生结弦是一位相当阳光帅气的运动员,他特别的善良,热...
爱情伤感文章要有文采! 爱情伤感文章要有文采!不是故事才可以要有文采!11不过,最真实的故事才是最感人的。
真的有‘运气’存在吗 真的有‘运气’存在吗那么人转运是以什么为界限的?是根据自己的生日吗?运气好办事容易成吗从局部看来有,...
古时候月亮升起和落下的地方各叫... 古时候月亮升起和落下的地方各叫什么名字?我知道太阳起在扶桑,落在禺谷,那么月亮呢??我想写篇散文,用...
有没有那种赚钱 布置房子 买家... 有没有那种赚钱 布置房子 买家具的手机游戏?模拟人生合集版女生的话可以玩养成游戏,如爱丽丝梦幻茶话会...
求几本能激励人的书 求几本能激励人的书我虽是城里人,但也是小地方的穷人,正要去大城市工作发展,请问有什么关于这样的励志的...