数据结构·顺序表

1·数据结构简介

学习数据结构与算法之前,一般是先学数据结构,方便之后学习算法,那么数据结构拆开介绍,就是数据 和 结构,数据,生活中到处都是,结构,就是数据存储的方式,即数据结构可以理解为计算机存储、组织数据的方式,那么我们学习数据结构无非就是学习数据在计算机里面的存储,组织方式,那么为什么需要数据结构,数据结构存在的意义是什么?

在餐厅发餐的时候,人们都有自己的号码牌,轮到自己的号码的时候也就是可以吃饭了,这里数据就是人们点餐的号码,存储的方式就是按照顺序存储,取餐的时候人们按照号码开始取餐,假定没有号码,也就没有了顺序,也就失去了秩序,那么处理发餐问题上的效率就会大打折扣,可以得出结论:数据结构可以提高处理事件的效率

其实我们接触C语言不久的时候就已经接触过数据结构了,数据结构就是存储数据,组织数据,那你想,存储数据的知识点,我们在前面学过……?

当然是数组了,数组是最基本的数据结构,它可以存储数据吧?可以组织数据吧?数组的章节我们 提到数组存储数据的时候内存空间是连续存储的,所以数组存储数据的方式就是连续存储,这点,我们会应用到之后的顺序表里面。

既然数组已经是数据结构了,为什么要学习其他的数据结构呢?假定这样的场景,我们定义了一个长度为10的数组,可是我们只存储一个数据,于是就浪费了9个数据,后我们又定义了一个长度为2的数组,可我们要存储10个数据,这个数组我们就用不上,也就是浪费空间了,那么我们真正想要的是什么呢?

我们真正想要的是一个能随着我们需求而改变的数据结构,这个时候就会说了,柔性数组呢?柔性数组大小确实是可以随需求的改变而改变,可是柔性数组也是存在条件的,数组是最后的成员,局限性是只能存储一种类型的数据,我们需要的不是一种数据,有时候我们需要多种数据糅合在一起,比如通讯录,那么现在,就进入到基于数组实现的更高级的数据结构里面——线性表。

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 ⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。


2·顺序表

顺序表是线性表的一种,底层逻辑是数组,那么它和数组的区别在哪里呢?
可以理解为是苍蝇馆子和米其林餐厅的区别,东西都是一样的,都可以存储东西,但是米其林可以提供其他的配菜,可以实现更多的”功能“,比如数组一次只能存一种数据,顺序表一次可以存储多种数据,顺序表可以通过多种接口的实现,来实现更多的功能,比如对数据进行增删查改。

顺序表分为两种,静态顺序表,动态顺序表。

#define N 7
typedef int Datatype;
struct SeqList
{Datatype arr[N];//定长数组int size;//有效数据个数
};

这就是静态顺序表的实现,看似平平无奇,实际就确实平平无奇,让人疑惑的可能是为什么对int进行重命名,假定这样一个场景,在一个容器里面全都是int,老板让你把int全部改成float,你是一个一个改吗?倘若只有几十个还好,如果有几千个呢?那就麻烦了,所以我们不妨在int放进容器的时候就对它进行重命名,之后如果更换,就只需要改一个int就行了,所以这里的重命名是为了之后数据类型的易转换。

typedef struct Seqlist
{Datatype* arr;int size;//有效数据个数int capacity;//开辟的空间大小
}SL;

这就是动态顺序表的实现,因为是动态的,所以使用指针来指向这块空间是最好的,注意,指针类型是我们可以自己定义的,最基础的就是整型之类的,比如存一些数,高级一点的就是结构体指针,也就是后面的通讯录会用到,我们开辟了空间之后,需要两个计数器,一个是计算里面的有效数据个数,一个是计算开辟的空间大小。


3·顺序表的初始化和销毁

我们创建的动态顺序表里面一共就三个成员,一个指针,两个整型,那么初始化无非就是给NULL或者0:

void SeqlistInit(SL s1)
{s1.arr = NULL;s1.size = s1.capacity = 0;
}

我们创建一个函数,用来初始化,看似没有问题,实际上当我们调试代码的时候,会发现我们真正需要的结构体并没有被初始化,因为这里我们使用的是传值,实际上初始化的只是临时拷贝的那个结构体,改进方法就是改用传址调用,为了方便起见,后面所有有关顺序表的操作我们都使用传址:

void SeqlistInit(SL* ps)
{ps->arr = NULL;ps->size = ps->capacity = 0;return 0;
}

同样为了之后方便实现顺序表通讯录,我们可以让顺序表有关的函数和源代码放在单独的头文件和源文件里面,记得要在头文件里面写上需要用到的其他的头文件。

其实销毁和初始化是大同小异,区别在于销毁需要把空间还给操作系统,因为在销毁之前顺序表已经实现了多种功能,所以我们并不知道现在的指针是否为空,即需要判断一下:

void SeqlistDestroy(SL* ps)
{assert(ps);ps->arr = NULL;free(ps->arr);ps->capacity = ps->size = 0;
}

使用assert的话别忘了引用头文件assert.h,如果不想引用头文件也可以结合if语句进行判断,总之就是要判断就对了,因为返回值是void,所以直接返回就行了,不需要加上返回值。

void SeqlistDestroy(SL* ps)
{if (ps->arr == NULL){return;}//assert(ps);ps->arr = NULL;free(ps->arr);ps->capacity = ps->size = 0;
}

4·顺序表的扩容

我们创建好顺序表之后还没有开辟空间,那么在我们插入数据之前可能就会面对两种问题,一是指针指向的空间还没有开辟,二是指针指向的空间大小不够插入数据,这两种情况我们都需要进行扩容:

void SLCheckCapacity(SL* ps)
{if (ps->capacity == 0){ps->arr = (Datatype*)malloc(4 * sizeof(Datatype));ps->capacity = 4;}if (ps->capacity == ps->size){Datatype* tem = (Datatype*)realloc(ps->arr, 2 * ps->capacity * sizeof(Datatype));if (tem != NULL){ps->arr = tem;ps->capacity *= 2;}else {perror("realloc fail!");return;}}
}

在扩容之后,顺序表里面的空间大小也会随着改变,所以我们改变指针指向空间的同时还要改变一下空间计数的大小,有人可能就问了,为什么是两倍两倍的增加空间大小,每插入一个数据的时候就增加一块空间不好吗?仔细想想这样不会浪费空间,但是实际上效率会低下很多,假如插入数据个数多了,几万个,插入一个数据就调用一下这个函数,岂不是浪费了很多时间,至于为什么是二倍,就留个读者自行探索了,二倍是最不容易浪费空间的了。

有人问了,为什么扩容之后,原来的指针不用释放,因为realloc函数会自动释放空间。


5·顺序表的尾部插入/删除

在所有插入数据操作之前,我们都应该判断空间大小是否足够,其次再考虑插入的事,尾部插入数据的时候需要注意,sizeof个数会增加:

void SLPushback(SL* ps, Datatype x)
{assert(ps);SLCheckCapacity(ps);ps->arr[ps->size++] = x;
}

这里使用后置++,代码界面看起来就更简洁,要是单独拎出来加加也行,尾插就直接插入数据就行了,没什么太多需要注意的。

那么尾部删除数据怎么操作呢?

有的人可能会说,删除数据我们就把删除的数据置于0,size减减不就行了吗?

实际上,给删除的数据置于0也是给了一个数据,最好的办法就是直接size减减,因为删除即不再使用那个数据,ps->arr[ps->size]是能访问到的最远的数据,size减减之后就访问不了那个数据了,这也是一种删除:

void SLDeleteBack(SL* ps)
{assert(ps);ps->size--;
}

当然,判断指针是否为空还是很有必要的。


6·顺序表的头部插入/删除

顺序表头部插入的时候,需要注意的有空间大小是否足够,size的增加,移动的循环次数,头部插入的时候需要将所有的数据往后移动一个单位,所以用到while或者for是必不可少的:

void SLPushHead(SL* ps, Datatype x)
{assert(ps);SLCheckCapacity(ps);for (int i = ps->size; i > 0; i--){ps->arr[i] = ps->arr[i - 1];}ps->arr[0] = x;ps->size++;
}

我们让数据都往后移动一个单位,所以i从0开始显然不那么可行,于是就从size开始了,整体数据移动完之后,首元素放上我们要插入的数据就行,最后别忘了ps->size++。

头部删除也就是除了第一个数据,其他数据全部往前面移动一个单位,同时size减减,空间大小不变,因为挪动的数据个数少一个,所以循环次数少一次,当然,指针是否为空指针也是不能忘记的:

void SLDeleteHead(SL* ps)
{assert(ps);for (int i = 0; i < ps->size - 1; i++){ps->arr[i] = ps->arr[i + 1];}ps->size--;
}


7·顺序表的指定删除/指定位置之前插入

指定删除需要注意的是删除的位置不能为负数,也不能是超过size的位置,删除之后,后面的数据应该往前移动一个单位,size减减,指定位置删除的删法和头删有点像,就是后面的数据依次往前,覆盖了数据就行:

void SLDelete(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--;
}

多注意循环变量的控制。

指定位置之前插入,类比头插和尾插,先判断空间大小,其次移动数据是必要的,size也是要++的,注意这里的pos是下标哦:

void SLPush(SL* ps, int pos, Datatype x)
{assert(ps);assert(pos >= 0 && pos <= ps->size);SLCheckCapacity(ps);for (int i = ps->size; i > pos; i--){ps->arr[i] = ps->arr[i - 1];}ps->arr[pos] = x;ps->size++;
}

8·顺序表的打印

打印是很简单的,就是一个for循环的事,我们先加上一下数据:

#include "Seqlist.h"
int main()
{SL s1;SeqlistInit(&s1);//初始化SLPushHead(&s1, 5);SLPushHead(&s1, 4);SLPushHead(&s1, 3);SLPushHead(&s1, 2);SLPushHead(&s1, 1);SLPushBack(&s1, 1);//1 2 3 4 5 1SLPrint(&s1);SLPush(&s1, 3, 100);//1 2 3 100 4 5 1SLPrint(&s1);SLDeleteHead(&s1);//2 3 100 4 5 1SLPrint(&s1);SLDelete(&s1, 4);//2 3 100 5 1SLPrint(&s1);SeqlistDestroy(&s1);//销毁return 0;
}
void SLPrint(SL* ps)
{assert(ps);for (int i = 0; i < ps->size; i++){printf("%d ", ps->arr[i]);}
}

最后的结果也是没有问题的。


9·顺序表的查找

查找无非就是进行遍历,最后返回我们要找的数据的下标就行了,遍历就要一个for循环就行了:

int SLFind(SL* ps,Datatype x)
{assert(ps);for (int i = 0; i < ps->size; i++){if (x == ps->arr[i]){return i;}}return -1;
}

然后结合实际效果看看:

以上就是顺序表的基本功能实现,下一篇介绍顺序表实现通讯录


感谢阅读!

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

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

相关文章

支付宝小程序实现类似微信多行输入

先来看看微信小程序输入框展示效果&#xff1a; 输入超过 8 行的时候会出现滚动&#xff0c;这样做的好处就是输入框不会直接顶到页面最顶部。 支付宝小程序实现多行输入框&#xff1a;使用textarea多行输入框实现 思路一&#xff1a; textarea 标签设置max-height, 标签自…

【Git教程】(二)入门 ——关于工作区与版本库、版本提交、查看信息、克隆、推送与拉回的简单介绍 ~

Git教程 入门 1️⃣ 准备Git 环境1.1 创建版本库1.2 首次提交1.3 显示历史 2️⃣ Git 的协作功能2.1 克隆版本库2.2 从另一版本库中获取修改2.3 从任意版本库中取回修改2.4 创建共享版本库2.5 用 push 命令上载修改2.6 Pull 命令&#xff1a;取回修改 &#x1f33e; 总结 如果…

跳表是一种什么样的数据结构

跳表是有序集合的底层数据结构&#xff0c;它其实是链表的一种进化体。正常链表是一个接着一个用指针连起来的&#xff0c;但这样查找效率低只有O(n)&#xff0c;为了解决这个问题&#xff0c;提出了跳表&#xff0c;实际上就是增加了高级索引。朴素的跳表指针是单向的并且元素…

ArcgisForJS如何访问Arcgis Server?

文章目录 0.引言1.准备ArcGIS相关工具2.创建含有ArcSDE地理数据库的MXD文件3.注册ArcSDE地理数据库4.发布数据到Arcgis Server5.ArcgisForJS访问ArcGIS Server数据 0.引言 ArcGIS API for JavaScript 是一个用于在Web和移动应用程序中创建交互式地图和地理空间分析应用的库。它…

解决MobaXterm网络错误连接超时问题

报错页面&#xff1a; 报错原因&#xff1a; ①网络断开了 ②网络端口&#xff0c;端口号改变 解决办法&#xff1a; ①重新连接网络按R ②固定端口号 第一步&#xff1a;编辑------>虚拟机网络编辑器&#xff08;我的Linux在虚拟机里&#xff09; 第二步&#xff1a;用…

抽象工厂模式 Abstract Factory

1.模式定义: 提供一个创建一系列相关或互相依赖对象的接口&#xff0c;而无需指定它们具体的类 2. 应用场景: 程序需要处理不同系列的相关产品&#xff0c;但是您不希望它依赖于这些产品的 具体类时&#xff0c; 可以使用抽象工厂 3.优点: 1.可以确信你从工厂得到的产品彼…

Chrome插件精选 — 缓存清理

Chrome实现同一功能的插件往往有多款产品&#xff0c;逐一去安装试用耗时又费力&#xff0c;在此为某一类型插件挑选出比较好用的一款或几款&#xff0c;尽量满足界面精致、功能齐全、设置选项丰富的使用要求&#xff0c;便于节省一个个去尝试的时间和精力。 1. Chrome清理大师…

Elasticsearch:使用 ELSER v2 进行语义搜索

在我之前的文章 “Elasticsearch&#xff1a;使用 ELSER 进行语义搜索”&#xff0c;我们展示了如何使用 ELESR v1 来进行语义搜索。在使用 ELSER 之前&#xff0c;我们必须注意的是&#xff1a; 重要&#xff1a;虽然 ELSER V2 已正式发布&#xff0c;但 ELSER V1 仍处于 [预览…

【算法 - 动态规划】最长回文子序列

上篇文章中&#xff0c;我们学习一个新的模型&#xff1a; 样本对应模型&#xff0c;该模型的套路就是&#xff1a;以结尾位置为出发点&#xff0c;思考两个样本的结尾都会产生哪些可能性 。 而前篇文章中的 纸牌博弈问题 属于 [L , R]上范围尝试模型。该模型给定一个范围&…

C 嵌入式系统设计模式 08:硬件代理模式

本书的原著为&#xff1a;《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》&#xff0c;讲解的是嵌入式系统设计模式&#xff0c;是一本不可多得的好书。 本系列描述我对书中内容的理解。本文章描述访问硬件的设计模式之一&…

【C++语法基础】3.常用数学运算和位运算技巧(✨新手推荐阅读)

前言 在C编程中&#xff0c;数学运算是非常基础和常用的功能。C提供了多种数学运算符和函数&#xff0c;用于执行基本的数学计算&#xff0c;如加减乘除、取模运算以及位运算等。 一、加减乘除四则运算 C中的基本算术运算符包括加法()、减法(-)、乘法(*)、除法(/)。这些运算…

Chrome关闭时出现弹窗runtime error c++R6052,且无法关闭

环境&#xff1a; Chrome 版本121 Win10专业版 问题描述&#xff1a; Chrome关闭时出现弹窗runtime error cR6052&#xff0c;且无法关闭 解决方案&#xff1a; 1.任务管理器打开&#xff0c;强制结束进程 2.再次打开谷歌浏览器&#xff0c;打开设置关于Chrome&#xff0…

IO进程线程day5作业

1、使用多线程完成两个文件的拷贝&#xff0c;第一个线程拷贝前一半&#xff0c;第二个线程拷贝后一半&#xff0c;主线程回收两个线程的资源 代码&#xff1a; #include<myhead.h>//定义文件拷贝函数 int copy_file(int start,int len) {int srcfd,destfd;//以只读的形…

Vue3之ref与reactive的基本使用

ref可以创建基本类型、对象类型的响应式数据 reactive只可以创建对象类型的响应式数据 接下来让我为大家介绍一下吧&#xff01; 在Vue3中&#xff0c;我们想让数据变成响应式数据&#xff0c;我们需要借助到ref与reactive 先为大家介绍一下ref如何使用还有什么注意点 我们需…

解决弹性布局父元素设置高自动换行,子元素均分高度问题(align-content: flex-start)

案例&#xff1a; <view class"abc"><view class"abc-item" v-for"(item,index) in 8" :key"index">看我</view> </view> <style lang"less">.abc{height: 100px;display: flex;flex-wrap: …

Web基础②nginx搭建与配置

目录 一.Nginx概述 1.定义 2.Nginx模块作用 &#xff08;1&#xff09;main模块 &#xff08;2&#xff09;stream服务模块 &#xff08;3&#xff09;邮件服务模块 &#xff08;4&#xff09;第三方模块 &#xff08;5&#xff09;events模块 &#xff08;6&#xff…

Python 进阶语法:JSON

1 什么是 JSON&#xff1f; 1.1 JSON 的定义 JSON 是 JavaScript Object Notation 的简写&#xff0c;字面上的意思是 JavaScript 对象标记。本质上&#xff0c;JSON 是轻量级的文本数据交换格式。轻量级&#xff0c;是拿它与另一种数据交换格式XML进行比较&#xff0c;相当轻…

Sora--首个大型视频生成模型

Sora--首个大型视频生成模型 胡锡进于2024年2月20日认为&#xff1a;台当局怂了 新的改变世界模拟器视觉数据转换视频压缩时空补丁&#xff08;Spacetime Laten Patches&#xff09;视频生成扩展变压器算法和模型架构结语 胡锡进于2024年2月20日认为&#xff1a;台当局怂了 **T…

六大设计原则 (SOLID)

一、设计原则概述 古人云: 有道无术,术可求.有术无道,止于术. 而设计模式通常需要遵循一些设计原则,在设计原则的基础之上衍生出了各种各样的设计模式。设计原则是设计要求,设计模式是设计方案,使用设计模式的代码则是具体的实现。 设计模式中主要有六大设计原则,简称为SOL…

【云原生】持续集成持续部署

本文主要总结CI/CD的流程&#xff0c;不会详细介绍每个知识点。 啥是集成&#xff1f;啥是部署&#xff1f; 集成&#xff0c;就是把应用程序、相关环境、配置全局打包放在一个容器中的操作。部署就不解释了。 CI/CD 如果是自己手动部署的话&#xff0c;流程应该是这样的&am…