数据结构---顺序表实现

news2024/10/7 2:00:14

目录

1.顺序表

2.动态顺序表的实现

(4)顺序表初始化

(5)顺序表销毁

(6)顺序表的插入

a.尾插

b.头插

(7)顺序表的删除

a.尾删

b.头删

(8)指定位置之前插入

(9)指定位置删除

(10)顺序表查找数据

3.我的心得体会(可跳过)

4.顺序表完整代码

(1)seqlist.h文件

(2)seqlist.c文件

(3)test.c文件


1.顺序表

数据结构就是计算机存储,组织数据的方式;

顺序表(线性表的一种,线性表是具有相同特性的数据结构的结合)就是一种数据结构,顺序表的本质就是数组;为什么数组存在了,还要有顺序表呢?

因为涉及特殊的操作,增加元素,删除元素,修改元素,插入数据,我们就要先计算数组的元素的个数,再进行相应的操作,顺序表的功能更加全面,含有增删查改的一些方法;

顺序表分为静态顺序表和动态顺序表:

(1)静态:定长的,使用size指定元素的个数;

(2)动态:长度是不确定的,使用size是数组元素的个数,使用capacity记录空间的大小;

静态顺序表是长度确定的,我们给他的空间小了,就会造成数据的丢失,空间给大了,会造成空间的浪费;

动态顺序表,我们可以进行增容,所以我们推荐动态顺序表;

2.动态顺序表的实现

(1)首先我们知道顺序表的本质就是数组,数组里面的元素可以是int,char等等的任意类型,所以我们在定义结构体的时候不能把数组元素的数据类型写死,而是使用宏进行替换,这样当数组的元素的数据类型发生改变的时候,我们就可以直接在宏定义的里面进行修改;

(2)顺序表的英文全称sequense list我们使用sl代表的是顺序表;sldatatype表示的是顺序表里面的数组的数据类型;

(3)为了方便我们的管理,我们新建3个文件,一个是sqlist.h的头文件,一个是sqlist.c的源文件,一个是test.c的源文件,这三个文件的作用是不一样的,头文件负责一些声明和定义,sqlist就是增删查改的具体函数的实现,test.c文件用来测试我们的顺序表的实现;

(4)顺序表初始化

我们首先要在头文件里面定义顺序表,生命我们的初始化的函数:

1.因为源文件里面的函数在使用的时候要包含我们的头文件,第一行的#pragma once就是为了防止头文件被多次包含;

2.我们头文件里面包含stdio.h,stdlib.h(动态内存管理的函数要包含这个文明),头文件里面包含之后,源文件里面包含头文件,就可以不进行包含了;

3.我们定义一个结构体struct seqlist里面的是结构体成员,数组(大小未知),数组元素的个数,空间的大小,因为为了简化我们每次定义结构体变量都要写struct seqlist,我们进行typedef的重定义,定义名字为sl;这样我们定义变量sl  s,就相当于struct  seqlist   s;比较简洁;

4.我们假定数组元素的类型都是int类型,我们进行typedef重定义,如果想要修改为char类型,只需要在第四行进行修改即可;

5.第12行是进行函数的声明,函数的声明和函数的定义第一行是一样的,就是结尾加上了分号;

顺序表的源文件:

测试函数的源文件:

6.可见,两个源文件都包含了头文件,我们在测试的函数里面重新定义了一个sltest01函数,在主函数里面进行调用;

7.我们如果进行传值,达不到真正的初始化的目的,应该传递指针,这样形参的改变才会影响到实参,我们在test01里面先定义了一个结构体的变量,把这个变量的地址传递到顺序表源文件里面进行初始化,初始化的时候,使用结构体成员访问操作符把第一个指针置为空指针,第二个和第三个元素初始化为0;

8.调试观察:

我们进行调试,刚开始我们自己定义的sl1变量是随机值,当初始化完成之后,就可以发现arr变成了空指针,size和capacity都是被初始化为0了,这样就算测试成功,达到我们初始化的目的;

(5)顺序表销毁

初始化之后,我们就要对顺序表填充功能(增删查改),最后进行销毁,我们先看一下如何进行销毁:

顺序表的源文件里面,我们要销毁,就是释放掉数组开辟的空间,然后把arr置为空指针,size和capacity全部赋值为0;

(6)顺序表的插入

顺序表的插入分为头部插入和尾部插入,我们首先看尾部插入(尾插):

a.尾插

我们上面的举例是数组的空间比较大,我们可以直接插入,当数组元素的个数和我们的空间容量的大小相同时,我们就需要使用动态内存管理的相关的函数开辟内存空间,然后进行数据的插入:

头文件里面进行函数的声明:


顺序表的源文件里面的注意事项比较多,我们一一说明:

1.我们首先能够想到的就是插入数据,然后把size++;

2.我们需要判断是否有空间能够让我们插入数据,否则我们需要进行增容,增容就是使用realloc函数开辟空间,呢么需要开辟多大的空间呢?

这个一次开辟多大的空间需要使用数学里面概率论的知识,我们这里只要记住结论:一次按照原来的两倍进行增容就可以了;

3.但是我们前面进行初始化的时候,capacity==size=0,如果直接进行capacity*2,得到的结果就是0,因此我们需要提前进行设置;

4.怎么进行设置呢,这个地方我们就用到了三目运算符,如果capacity=0,我们就会把空间的容量,设置为4,否则就在原来的基础上面乘以2(这个就是代码里面的三目运算符表达式的含义了);

5.我们不能直接使用ps->arr进行接收,我们应该了解realloc的原理,如果开辟成功,我们就可以使用,如果开辟失败,那么我们原来的数据就没了,因此我们需要定义一个临时的相同的数据类型的指针变量进行接收,如果不是空的,我们在传递给ps->arr;

6.增容之后,我们就需要把新的容量赋值给capacity,实现空间的增大,为了增加代码的健壮性,我们进行断言(防止传递过来的空指针导致程序的崩溃);


测试的源文件里面我们插进去的数字是8,我们也可以多次插入,看看调试的时候的增容的实现过程:

b.头插

尾部插入数据,同样需要判断数组的容量的大小够不够,还是要用到头插里面一样的代码,因此我们可以把代码封装成为一个函数check,这样我们在头插,尾插可以直接进行调用,提高我们的代码的可读性和实用性:

因为,我们顺序表的功能完成之后,我们要进行测试,为了简单起见,我们可以把头插,尾插后的顺序表里面的数组元素打印出来进行验证,因此我们定义了slprint函数:

我们有了这个判断的函数以后,重新实现尾插和头插:

下面的两段代码对比之后就可以发现,异曲同工,都是先进行指针的断言和size的断言,

尾插比较简单:判断之后,直接在size下标的位置加上数字就可以了;

头插相对而言比较复杂:先进行断言和判断,因为是头插,我们呢要后面的一个数据覆盖前面的数据就,然后插入的数据放到0下标就可以了,这个就要用到for循环,最重要的是循环条件的判断,因为最后的覆盖是arr[1]=arr[0],所以循环的终止条件就是i=1,但是不能是0,因此i>0就可以了;

(7)顺序表的删除

a.尾删

尾删也是先进行断言,然后进行减小容量(比较简单,但是不好想明白,我是这样的);

我们就可以理解为原来有1 2 3 4这四个数,我们把size--,size就变为3,这样进行打印的时候,就是以size作为判断的条件的,没有删掉,但是打印结果不会显示,因为那个末尾的数字已经不再我们的范围里了,好像已经“删除”了,(因为本质上就没有删,只不过不打印,不属于我们的范围而已了);

b.头删

头删也是进行的移动覆盖,前面的头插是集体向后移,为第一个数字的插入腾空间,头删就是集体向前移,把最前面的数据给覆盖掉,这里移动的时候同样用到了循环,循环终止条件的判断尤为重要,最后的移动覆盖是arr[size-2]=arr[size-1],同理,1234这四个数字,2覆盖1,3覆盖2,4覆盖3,最后就是2344,其实最后的4覆盖前面之后自己还是存在的,但是我们打印的时候不打印他,因为我们的size--了啊,先当于循环的时候就不会这第二个四这个地方;

(8)指定位置之前插入

先进行断言,范围判断;把指定位置腾出来(循环向后移动),在向这个位置放进去数据,增大size;

(9)指定位置删除

先进行断言,从pos位置开始,循环向前移动数据,把指定位置的数据给覆盖掉,这样就实现指定位置的数据删除,最后size--;

(10)顺序表查找数据

我们使用循环遍历整个数组就可以了;我们这里的返回值是int类型,这个用来显示我们是否找到了,找到了就返回数字的下标,没有就返回无效的下标(因为肯定没有数字的下标是-1);

当然为了让打印的结果更加的直观,我们可以测试的时候对函数的返回值进行判断,有效就说找到了指出下标,没找到就显示未找到。

3.我的心得体会(可跳过)

(1)写到这个位置真正完成了这个全部功能的实现,我也如释重负,但是我知道自己还没有完全掌握,“纸上得来终觉浅,绝知此事要躬行”,只有多多巩固,才能真正的自己写出来;

(2)我自己第一次遇到顺序表的时候,听了一遍也是一头雾水,例如里面的各种重新命名,参数的设置(实参形参),但是当你真正理解一个功能的实现方法之后,其他的就不是问题了,重要的是实现功能的方法逻辑;

(3)现在回想一下,这些功能的实现时候,有许多相似的地方,例如进行断言(指针是否为空指针,空间的容量是否为零,为零的话我们就不能进行删除等操作);

(4)当然他们也有区别,例如在进行循环的时候,循环终止条件的判断,移动覆盖,从前向后移动还是从后向前移动,例如头插就是从后向前移动(腾开位置),头删就是从前向后移动(覆盖掉位置);

(5)我觉得,这个里面比较难的还是循环终止条件的判断,我们不要去用脑子想,要画出来,写出最后一次循环的时候的情况,找出对应的下标和已知的参数的关系,再进行判断;

(6)当然,还有我们进行指定位置之前的插入pos可以等于size的大小,但是进行指定位置指定位置的删除的时候,我们就不能让pos==size了,这个能不能取等,我们也要举例验证;

(7)在test.c文件里面进行测试的时候,例如指定位置的删除,我们要测试3种情况,在第一个位置删除(也就是头删),在中间的位置删除,在最后的位置删除(也就是尾删),对于这样的,既应该满足中间删除的普通情况,也应该满足头删尾删的特殊情况,这几种情况都要进行测试,都通过才证明我们的代码是没有问题的;

(8)感谢你看到这里,听我细细道来,路漫漫其修远兮,we将上下而求索,加油💪!

4.顺序表完整代码

(1)seqlist.h文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int sldatatype;

typedef struct seqlist
{
	sldatatype* arr;
	int size;
	int capacity;
}sl;

//顺序表的初始化
void slinit(sl* ps);

//顺序表的销毁
void sldestory(sl* ps);

//顺序表的尾部插入数据
void slpushback(sl* ps, sldatatype x);

//顺序表的数组元素的打印
void slprint(sl s);

//顺序表的头部插入数据
void slpushfront(sl* ps, sldatatype x);

//顺序表的尾删
void slpopback(sl* ps);

//顺序表的头删
void slpopfront(sl* ps);

//顺序表的打印
void slprint(sl s);

//顺序表的指定插入
void slinsert(sl* ps, int pos, sldatatype x);

//顺序表的指定删除
void slerase(sl* ps, int pos);

//顺序表的数据的查找
int slfind(sl* ps, sldatatype x);

(2)seqlist.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"seqlist.h"
void check(sl* ps)
{
	if (ps->size == ps->capacity)
	{
		//我们插入数据,需要增大空间,动态内存开辟空间
		int newcapacity = ps->arr == 0 ? 4 : 2 * ps->capacity;
		sldatatype* temp = realloc(ps->arr, newcapacity * 2 * sizeof(sldatatype));
		if (temp == NULL)
		{
			perror("realloc");
			exit(1);//直接退出,不再继续执行
		}
		ps->arr = temp;
		ps->capacity = newcapacity;
	}
}
//顺序表的初始化
void slinit(sl* ps)
{
	ps->arr = NULL;
	ps->size = 0;
	ps->capacity = 0;
}
//顺序表的销毁
void sldestory(sl* ps)
{
	if (ps->arr != NULL)
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}
//顺序表的尾部插入数据
void slpushback(sl* ps, sldatatype x)
{
	//对传进来的指针进行断言
	assert(ps);
	//判断数组大小和空间的容量是否相同
	check(ps);
	ps->arr[ps->size] = x;
	ps->size++;
}
//顺序表的头部插入数据
void slpushfront(sl* ps, sldatatype x)
{
	assert(ps);
	check(ps);
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	ps->size++;
}
//顺序表的打印
void slprint(sl s)
{
	for (int i = 0; i < s.size; i++)
	{
		printf("%d ", s.arr[i]);
	}
	printf("\n");
}
//顺序表的尾删
void slpopback(sl* ps)
{
	assert(ps);
	assert(ps->size);//如果我们顺序表本来就没有数据,我们进行减减就会变为-1了,显然不对,进行断言
	ps->size--;//渐渐就减少了范围,直接把最后的元素删除,就相当于最后一个元素不属于顺序表了
}
//顺序表的头删
void slpopfront(sl* ps)
{
	assert(ps);
	assert(ps->size);
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//最后是arr[size-2]=arr[size-1];
	}
	ps->size--;
}

//顺序表的指定位置之前插入
void slinsert(sl* ps, int pos, sldatatype x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	check(ps);
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}

//顺序表的指定删除
void slerase(sl* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

//顺序表的数据的查找
int slfind(sl* ps, sldatatype x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		//找到了
		if (ps->arr[i] == x)
		{
			return i;
		}
	}
	//没有找到
	return -1;//无效的下标
}

(3)test.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"seqlist.h"
void sltest01()
{
	sl sl1;

	//顺序表的初始化
	slinit(&sl1);

	//顺序表的尾部插入数据
	slpushback(&sl1, 8);
	slpushback(&sl1, 9);
	slpushback(&sl1, 10);
	slpushback(&sl1, 11);

	//尾部插入之后打印
	slprint(sl1);

	//顺序表的头部插入数据
	slpushfront(&sl1, 5);
	slpushfront(&sl1, 6);

	//头部插入之后打印
	slprint(sl1);

	//顺序表的尾删
	slpopback(&sl1);
	slprint(sl1);

	//顺序表的头删
	slpopfront(&sl1);
	slprint(sl1);

	//顺序表的指定位置之前插入
	slinsert(&sl1, 1, 0);
	slprint(sl1);

	//顺序表的指定删除
	slerase(&sl1, 1);
	slprint(sl1);

	//顺序表的数据的查找
	int ret = slfind(&sl1, 100);
	if (ret < 0)
	{
		printf("没有找到\n");
	}
	else
	{
		printf("找到了,下标是%d\n", ret);
	}
	//顺序表的销毁
	sldestory(&sl1);
}

int main()
{
	//顺序表的功能测试函数
	sltest01();
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1573250.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jenkins插件集成api使用

jenkins配置插件&api使用 jenkins https://mirrors.jenkins.io/war-stable/2.222.1/ 包下载地址 jenkins镜像源修改 sed -i s/https:\/\/www.google.com/https:\/\/www.baidu.com/g default.json sed -i s/https:\/\/updates.jenkins.io\/download/https:\/\/mirrors.…

使用YOLOv8训练自己的【目标检测】数据集

文章目录 1.收集数据集1.1 使用开源已标记数据集1.2 爬取网络图像1.3 自己拍摄数据集1.4 使用数据增强生成数据集1.5 使用算法合成图像 2.标注数据集2.1确认标注格式2.2 开始标注 3.划分数据集4.配置训练环境4.1获取代码4.2安装环境 5.训练模型5.1新建一个数据集yaml文件5.2预测…

ASP.NET Core 模型绑定

&#x1f340;介绍 在C#中&#xff0c;特别是在ASP.NET Core中&#xff0c;模型绑定是一个将HTTP请求中的数据映射到C#对象的过程。它使得开发者能够方便地从请求中提取数据&#xff0c;而无需手动解析请求体、查询字符串、路由数据等。ASP.NET Core提供了多种特性&#xff08…

《债务与国家的崛起》西方民主制度的金融起源 - 三余书屋 3ysw.net

债务与国家的崛起&#xff1a;西方民主制度的金融起源 你好&#xff0c;今天我们来聊聊由英国知名经济与金融历史学家詹姆斯麦克唐纳所著的《债务与国家的崛起》这本书。19世纪世界历史上发生了一次巨变&#xff0c;即“大分流”。当时西方通过工业革命实现了科技和经济的飞速…

如何保护大模型API安全

大模型的崛起正在改变着我们对机器学习和人工智能的理解&#xff0c;它们不仅提供了令人惊叹的预测和分析能力&#xff0c;还在各行各业的应用中发挥着重要作用。通过提供 API&#xff0c;用户无需了解底层实现细节&#xff0c;使大型模型能够更好地与用户和应用程序进行交互&a…

深度学习相关知识

一.环境配置 1.cuda/cudnn安装&#xff1a; 安装cuda之前首先安装vs&#xff0c;vs版本从低到高安装。 a) 安装cuda&#xff1a;首先查看显卡支持的最高CUDA的版本&#xff0c;以便下载对应的CUDA安装包&#xff1b; cmd命令行&#xff1a;nvidia-smi,显示如下&#xff1a;…

【GPT-4 Turbo】、功能融合:OpenAI 首个开发者大会回顾

GPT-4 Turbo、功能融合&#xff1a;OpenAI 首个开发者大会回顾 就在昨天 2023 年 11 月 6 日&#xff0c;OpenAI 举行了首个开发者大会 DevDay&#xff0c;即使作为目前大语言模型行业的领军者&#xff0c;OpenAI 卷起来可一点都不比同行差。 OpenAI 在大会上不仅公布了新的 …

基于单片机多模式多样式跑马灯系统仿真设计

**单片机设计介绍&#xff0c;基于单片机多模式多样式跑马灯系统仿真设计 文章目录 一 概要二、功能设计三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机多模式多样式跑马灯系统仿真设计是一个集硬件仿真、软件编程和灯光控制于一体的综合性项目。以下是对该设…

Quantinuum与微软携手突破:开创容错量子计算新纪元

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

SystemC入门之测试平台编写完整示例:带同步输出的多路选择器

内容&#xff1a;SystemC入门书中的简单测试平台编写示例。 模块文件编写 带锁存输出的4选1多路器模型。输出在信号clock的正跳变沿时刻被锁存。 sync_mux41.h文件 #include <systemc.h>SC_MODULE(sync_mux41) {sc_in<bool> clock, reset;sc_in<sc_uint<…

【开源语音项目OpenVoice](一)——实操演示

目录 一、前菜 1、Python选择 2、pip源切换 3、ffmpeg配置问题 4、VSCode添加Jupyter扩展 二、配置虚拟环境 1、下载源码 方法一 直接下载源码压缩包 方法二 使用git 1&#xff09;git加入鼠标右键 2&#xff09;git clone源码 2、VSCode出场 1&#xff09;创建pyth…

C#操作MySQL从入门到精通(5)——查询数据

前言 在和MySql数据库交互的过程中,查询数据是使用最频繁的操作,本文详细介绍了查询数据的各种操作,包括查询一列数据、 查询两列数据、查询所有列数据、查询不重复的数据、查询指定行数据,绝对是C#操作MySql数据库史上最详细教程,能够帮助小白快速入门以及将这些功能迅速…

闪站侠洗护管理系统,洗衣洗鞋小程序软件定制,干洗连锁店软件系统搭建;

闪站侠洗护管理系统&#xff0c;洗衣洗鞋小程序软件定制&#xff0c;干洗连锁店软件系统搭建&#xff1b; 为了让每一个洗衣洗鞋工厂与门店的连接更加高效便捷&#xff0c;送洗流程更加简单轻松&#xff0c;拽牛科技倾心打造洗衣洗鞋管理软件。我们的目标是通过高效和优质的服务…

栈的详解和例题(力扣有效括号)

感谢各位大佬的光临&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 个人主页&#xff1a;LaNzikinh-CSDN博客 收入专栏:初阶数据结构_LaNzikinh篮子的博客-CSDN博客 文章目录 前言一.什么是栈二.栈的实现三.例题&#xff…

爬取学习强国视频小示例

因为需要爬取的视频数量并不是很大&#xff0c;总共需要将131个视频下载下来&#xff0c;所以就直接去手动找找视频的地址和名称保存下来的。由于页面是动态加载的&#xff0c;所以我们无法在网站源码中直接找到视频的超链接。设想是可以用Selenium模拟浏览器点击进行动态加载获…

excel统计分析——多项式回归

参考资料&#xff1a;生物统计学 多项式回归属于单变量曲线回归&#xff0c;但其形式和求解方法与多元线性回归相似。多项式回归的数学模型为&#xff1a; 令&#xff0c;&#xff0c;&#xff0c;&#xff0c;则 由于X不可逆&#xff0c;两边同时乘以X得&#xff0c;&#xff…

《QT实用小工具·十五》多种样式的开关控件

1、概述 源码放在文章末尾 目前实现了三种样式的开关控件按钮&#xff0c;如下所示&#xff1a; 项目部分代码如下所示&#xff1a; #ifndef IMAGESWITCH_H #define IMAGESWITCH_H/*** 图片开关控件 * 1. 自带三种开关按钮样式。* 2. 可自定义开关图片。*/#include <QWid…

使用Android完成案例教学

目录 题目&#xff1a;完成在Android平台下2个玩家分别利用2个手机连接在同一局域网下通过滑动摇杆分别使红飞机和黄飞机移动的开发。&#xff08;全代码解析&#xff09; 题目&#xff1a;完成在Android平台下2个玩家分别利用2个手机连接在同一局域网下通过滑动摇杆分别使红飞…

利用native的方式实现跨线程调用

简介 在OpenHarmony应用开发实践中&#xff0c;经常会遇到一些耗时的任务&#xff0c;如I/O操作、域名解析以及复杂计算等。这些任务如果直接在主线程中执行&#xff0c;将会严重阻塞主线程&#xff0c;影响后续任务的正常流程&#xff0c;进而导致用户界面响应延迟甚至卡顿。…

基于沙漏 Tokenizer 的高效三维人体姿态估计框架HoT

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;基于沙漏 Tokenizer 的高效三维人体姿态估计框架HoT1、研究背景2、提出方法3、模块详细3.1、什么是HoT3.2、HoT 框架3.3、Token 剪…