深入浅出:手把手教你实现顺序表

一、什么是顺序表

顺序表是一种数据结构,或者说,是数据在内存中存储和管理的一种方式。顺序表要求每个数据要从第一个位置开始,依次挨着放。这就很适合使用C语言中的数组来实现。

很多朋友可能会觉得,那有啥可以讲的?我们创建一个数组,在里面存储数据不就得了?其实没这么简单。这个数组要创建多大?如果创建太小,万一数据太多,不够存咋办?万一创建太大,却没这么多数据,那很多空间不就浪费了?所以,我们需要进行动态的内存管理。比如说,一开始就给4个空间,之后每次放满了就扩容,依次类推。

C语言里提供了动态内存管理的函数,分别是malloc,calloc,realloc和free。这些函数能够帮助我们很好地进行动态内存的管理。以上的这几个函数有一个特点,需要使用一个指针来指向这块动态管理的空间。除此之外,我们还需要两个变量,分别用来记录这块空间的大小,以及存储的有效数据的个数。综上,我们需要声明一个结构体来描述顺序表。


struct SeqList
{int* a;int size;int capacity;
};

其中,我们假设要存储的数据类型是int,所以需要一个int*的指针来管理这块空间。如果需要存储其他类型的数据,并且随时修改存储类型,可以使用typedef,用SLDateType来代替此处和下文中出现的int,这样方便以后修改存储类型。


typedef int SLDataType;

以上结构体中的size代表目前存储的有效数据的个数,一开始是0。而capacity则代表容量,也就是目前最多可以存储的数据个数。当size和capacity相等时,代表顺序表已满,此时就需要扩容。

需要说明的是,如果你嫌每次写struct SeqList很麻烦,也可以typedef一下。


typedef struct SeqList
{int* a;int size;int capacity;
}SL;

以下叙述中,我们假设已经创建了一个顺序表,并且拿到了它的地址psl。


SL sl;
SL* psl = &sl;

由于后面的操作都要对psl解引用,所以我们假设每次解引用前都断言了psl!=NULL。


assert(psl);

二、初始化

顺序表一开始没有存储任何数据,所以可以把先把结构体的内容都初始化成0。


psl->a = psl->size = psl->capacity = 0;

三、插入、删除

数组的下标是从0开始的,所以我们假定顺序表的下标也是从0开始。那么,我们如何在下标为pos的位置插入一个数据呢?其实很简单,只需要把pos及pos之后的位置向后挪动一格,此时pos位置就空出来了,就可以插入数据了。所以核心代码就一行,即如何挪动数据。我们需要知道起始位置是pos,往后挪动一格就到了pos+1。一共要挪动几个数据呢?举个例子,假设一共有10个数据,也就是size=10,假设pos是3,下标是从0开始的,pos前面的数据有0,1,2,总共3个数据,也就是说,pos前面的数据有pos个,那么pos及pos之后的数据就有size-pos个,这也是要挪动的数据的个数。我们使用memmove函数来挪动数据,注意不能使用memcpy,因为挪动前后的内存空间是有重叠的。


memmove(psl->a+pos+1, psl->a+pos, (psl->size-pos)*sizeof(int));

接着插入数据。


psl->a[pos] = x;
psl->size++;

这就完了吗?No!你忽略了一个重要的问题。一开始初始化时,psl为空,能直接解引用吗?还有万一size=capacity,能直接插入吗?所以每次插入前,都要检查一下容量是否充足,如果满了,要扩容!

检查容量是否充足很好办,如果size==capacity就满了,这包括两种情况,一种是capacity=size=0,另一种是随着size逐渐增长,size==capacity时就满了。

那如何扩容呢?这就可以使用realloc。注意当传给realloc的指针为NULL时,realloc的表现和malloc是一样的。所以我们可以把上面两种情况当做一种情况来处理。为了叙述方便,我们假设一开始给4个空间,后面每次都扩2倍,也就是说新的容量是:


int newCapacity = psl->capacity==0 ? 4 : 2*psl->capacity;

接着调用realloc函数:


int* tmp = (int*)realloc(psl->a, newCapacity*sizeof(int));

若tmp经过检查不为NULL,说明扩容成功。


psl->a = tmp;
psl->capacity = newCapacity;

知道了如何插入数据,删除数据也是同样的道理。只需要把pos位置之后的数据向前挪一格,就覆盖掉了pos位置的数据,相当于把pos位置的数据删除了。目标位置是pos,起始位置是pos+1,要挪动的数据个数是多少呢?在上面插入数据的讲解中,pos及pos之后的数据个数是size-pos,那么不包括pos的数据个数就是size-pos-1。


memmove(psl->a+pos, psl->a+pos+1, (size-pos-1)*sizeof(int));

最后不要忘了psl->size--。但是有个问题,如果一开始就没有数据,那就不能继续删了呀!所以删除之前要断言一下psl->size>0,才可以删除(但有了后面的讲解,其实不用断言这一点,具体原因后面说)。

其实,前面的讲述中漏掉了一点,那就是pos并不是任何值都可以的。在插入数据的时候,必须保证pos>=0 && pos<=psl->size,而在删除的时候,必须保证pos>=0 && pos<psl->size。pos>=0很好理解,因为下标从0开始。pos<size也很好理解,因为size表示有效数据个数。假设有6个数据,即size是6,下标就是0~5,pos就只能从这之间取。但是插入时为啥允许pos==size呢?还是假设有6个数据,size是6,已经有的数据下标是0~5,那如果是否允许在下标为6的位置插入呢?当然允许呀!因为在已经有的最后一个数据后面紧接着插入一个数据,并没有违反顺序表数据挨着挨着存储的原则。

由于删除顺序表的元素时有pos>=0,size>pos,故有size>0,所以不用断言这一点。

四、打印

只需要遍历这个数组然后打印即可。


for (int i = 0; i<psl->size; ++i)
{printf("%d ", psl->a[i]);
}
printf("\n");

五、查找、修改

查找也是遍历数组即可,非常简单。需要注意的是,pos必须要满足pos>=0 && pos<psl->size。具体原因,讲解插入删除时已经讲过。

如查找x:


for (int i = 0; i<psl->size; ++i)
{if (psl->a[i] == x)return i;
}return -1; // 找不到就返回负数

修改也很简单,访问下标为pos的数据并且改掉即可。


psl->a[pos] = x;

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

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

相关文章

R3LIVE项目实战(4) — 双目相机与激光雷达livox_camera_lidar_calibration联合标定

目录 步骤1: 环境配置 1.1 安装环境和驱动 1.2 安装依赖 1.3 下载源码&#xff0c;编译准备 1.4 程序节点概括 步骤2: 相机内参标定 2.1 前期准备 2.3 cameraCalib标定 2.4 内参结果 步骤3: 标定准备和数据采集 3.1 标定场景准备 3.2 连接雷达 3.3 连接相机 3.4 采…

基于全新电脑环境安装pytorch的GPU版本

前言&#xff1a; 距离第一次安装深度学习的GPU环境已经过去了4年多&#xff08;当时TensorFlow特别麻烦&#xff09;&#xff0c;现在发现安装pytorch的GPU版本还是很简单方便的&#xff0c;流程记录如下。 安装步骤&#xff1a; 步骤一&#xff1a;官网下载Anaconda Free…

Threejs里执行对象的多个动画

承接上文&#xff0c;本文讲述如何在Threejs里播放对象的多个动画&#xff0c;这也是研究了很久才解决的… 一 导出模型 在Blender里按照File->Export&#xff0c;选择glTF2.0 然后在弹框的右上角选择导出为glTF Embedded (.gltf) 这样就把模型导出来了&#xff0c;该模…

docker部署nginx,部署springboot项目,并实现访问

一、先部署springboot项目 1、安装docker&#xff1a; yum install docker -y 2、启动docker&#xff1a; service docker start 重启&#xff1a; service docker restart 3、查看版本&#xff1a; docker -v 4、使设置docker.service生效&#xff08;路径&#xff1a;…

qt第二天

#include "widget.h" #include "ui_widget.h" #include "QDebug" Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->resize(QSize(800,600)); //使用匿名对象&#xff0c;调用重…

HarmonyOS开发:超详细了解项目的工程结构

前言 系列文章目录&#xff1a; HarmonyOS开发第一步&#xff0c;熟知开发工具DevEco Studio 当我们熟练的掌握了DevEco Studio之后&#xff0c;就可以创建项目进行练习了&#xff0c;和市场上大多数IDE一样&#xff0c;DevEco Studio也给我们提供了很多的实例模板&#xff0c…

ElasticSearch-集成ik分词器

本文已收录于专栏 《中间件合集》 目录 背景介绍版本选择优势说明集成过程1.下载安装包2.解压安装包3.重启ElasticSearch服务3.1通过ps -ef | grep elastic查看正在启动的es进程号3.2使用kill -9 xxx 杀死进程3.3使用 ./elasticsearch 启动es服务 分词测试细粒度分词方式分词请…

【面试题】UDP和TCP有啥区别?

UDP UDP协议全称是用户数据报协议&#xff0c;在网络中它与TCP协议一样用于处理数据包&#xff0c;是一种无连接的协议。在OSI模型中&#xff0c;在第四层——传输层&#xff0c;处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点&#xff0c;也就…

基于stm32的烟雾浓度检测报警proteus仿真设计(仿真+程序+讲解)

基于STM32的烟雾浓度检测报警仿真设计(仿真程序讲解&#xff09; 1.主要功能2.仿真3. 程序4. 资料清单&下载链接 基于STM32的烟雾浓度检测报警仿真设计(仿真程序讲解&#xff09; 仿真图proteus 8.9 程序编译器&#xff1a;keil 5 编程语言&#xff1a;C语言 设计编号&a…

使用TPDSS连接GaussDB数据库

TPDSS是GaussDB官方提供的数据库连接工具&#xff0c;可以在TPDSS查看GaussDB的建库建表语句&#xff0c;于GaussDB使用兼容性比较好&#xff0c;由于TPDSS查找比较麻烦&#xff0c;下面给出了下载链接地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1Lqcu3KriE47…

打破数据孤岛,实现文档数据互通

随着数字经济加速发展&#xff0c;企业数字化转型正向更深层次推进。非结构化数据量也正在飞速增长&#xff0c;这些数据以文档、图片、音频等形式散落在组织内部&#xff0c;这给数据的整理和统一利用增加了难度。由于部门、应用、框架、多云环境等原因形成非结构化数据孤岛。…

JavaWeb 速通Ajax

目录 一、Ajax快速入门 1.基本介绍 : 2.使用原理 : 二、Ajax经典入门案例 1.需求 : 2.前端页面实现 : 3. 处理HTTP请求的servlet实现 4.引入jar包及druid配置文件、工具类 : 5.Domain层实现 : 6.DAO层实现 : 7.Service层实现 : 8.运行测试 : 三、JQuery操作Ajax 1 …