手撕数据结构 —— 顺序表(C语言讲解)
目录
1.顺序表简介
什么是顺序表
顺序表的分类
2.顺序表的实现
SeqList.h中接口总览
具体实现
顺序表的定义
顺序表的初始化
顺序表的销毁
打印顺序表
编辑
检查顺序表的容量
尾插
尾删
编辑
头插
头删
查找
在pos位置插入元素
删除pos位置的值
编辑
修改
3.完整代码附录
SeqList.h
SeqList.c
1.顺序表简介
什么是顺序表
顺序表是一种用物理地址连续的存储单元 依次存储数据元素的线性结构。
等等,物理地址连续的存储单元…… 这不就是我们在C语言中学习过的数组吗?是的,我们可以这样理解,顺序表的底层物理结构就是数组。需要注意的是,顺序表的底层物理结构是数组,并不是说顺序表就是数组。顺序表要求依次存储,也就是存储数据元素的时候,从第一个位置紧挨着存储,对顺序表进行增、删、改、查操作之后的顺序表也必须满足这一特性。
顺序表的分类
顺序表一般可以分为静态顺序表和动态顺序表。静态顺序表使用定长数组存储元素,动态顺序表使用动态开辟的数组存储元素。
静态顺序表示意图:

动态顺序表示意图:
静态顺序表只适用于存储空间大小明确的场景。如果静态顺序表的大小N定大了,就会造成空间的浪费,如果N定小了就会造成空间不够用。在实际应用中,我们很少能够确切的知道需要处理的数据元素的大小,所以,静态顺序表在实际应用中并不实用,现实中基本都是使用动态顺序表,根据需要动态的分配空间。
2.顺序表的实现
基于上述原因,我们实现动态顺序表。我们需要创建两个文件,分别是SeqList.h和SeqList.c,SeqList.h中用来存放接口的声明,SeqList.c中存放接口的定义。(文章末尾附完整代码)
SeqList.h中接口总览
实现顺序表,主要实现顺序表的增删改查,需要实现以下接口。

具体实现
顺序表的定义
我们定义的顺序表的底层物理空间为动态开辟的,用变量a指向这块空间,用变量size记录存储的有效数据的个数,用变量capacity表示容量空间的大小,当size == capacity的时候就需要进行扩容操作了。

顺序表的初始化
对于初始化操作,我们主要初始化struct SeqList结构体变量中的成员即可。
- 对于最开始的容量可以根据实际需求动态设置即可;
- size初始化为0,因为还没有向顺序表中添加元素。
- capacity的初始值和动态申请的空间大小一致。

注意:
- 我们初始化顺序表的时候,指向顺序表的指针不能为空,我们使用assert()函数暴力的检查,如果ps指针为空,程序就会崩溃。使用assert()函数的时候,需要包含头文件assert.h。
- 我们还需要注意动态内存分配失败的情况,如果初始化的时候,动态分配内存失败,那么后面的程序都没有执行的必要了,我们使用exit()函数终止整个程序。使用exit()函数的时候,需要包含头文件stdlib.h。
后面的所有操作都需要判断指向顺序表的指针是否为空的情况(该指针不能为空!!!)
顺序表的销毁
销毁顺序表时,主要销毁的是struct SeqList结构体变量中的成员
- 对于变量a,需要将变量a指向的空间释放,也就是把使用权归还给操作系统;并将a置为空,避免出现野指针。
- 变量size和变量capacity置为0即可。

打印顺序表
直接循环打印即可。
检查顺序表的容量
当size == capacity的时候,说明顺序表的所有空间都用来存储有效元素了,当再次往顺序表中插入元素的时候,就没有空间了,需要扩容,我们选择2倍扩容。
- 扩容的时候,我们选择realloc函数。该函数会自动的帮我们申请一块空间,同时将原数组中的内容拷贝至新数组中,并且释放旧的数组空间,返回新空间的起始地址。

几个注意点:
- 检查realloc函数执行是否失败,如果失败,终止整个程序。
- 返回的新空间的起始地址需要赋值给变量a,因为我们始终认为struct SeqList结构体变量中的成员a才是指向数组空间的。
- 最后不要忘记放大变量capacity的值。
尾插
当进行插入操作的时候,我们需要判断顺序表的容量有没有满,如果满了就扩容,这一步可以复用我们前面实现的SLCheckCapacity()函数。
- size表示有效元素的个数,作为下标的话就是有效元素的下一个位置。
- 不要忘记将size++。
进行任何插入操作时,我们都需要先检查容量是否足够。通过复用SLCheckCapacity()函数即可。

可以看出顺序表尾插的时间复杂度是O(1)。
尾删
进行尾部删除元素的时候,我们可以直接让size--即可,因为size表示存储的有效元素的个数,当size--之后,最后一个元素就不是有效元素了,可以被覆盖。当然,你也可以将最后一个有效元素修改为指定值之后再进行size--操作,但这并没有什么意义。
进行删除操作的时候,需要确保数组中存有有效元素。我们同样可以使用assert()函数进行暴力的检查。
可以看出顺序表尾删的时间复杂度也是O(1)。
我们可以得出结论:顺序表在尾部进行插入和删除的效率非常高,时间复杂度都是O(1),因此,顺序表适合进行尾插尾删操作。
注意:后面所有的删除操作都需要确保数组中存有有效元素。
头插
进行头部插入时,也就是在数组的最开始位置插入元素,我们需要将所有的有效元素向后移动一个位置,然后再插入元素。

可以看出顺序表头插的时间复杂度是O(N),如果频繁大量的进行头插操作,效率将非常低下。
头删
进行头删操作时,只需要将除了第一个有效元素后面的元素都往前移动一个位置,然后进行size--操作即可。

可以看出顺序表头删的时间复杂度也是O(N),如果频繁大量的进行头删操作,效率将非常低下。
我们可以得出结论:顺序表在头部进行插入和删除的时间复杂度都是O(N),因此,顺序表不适合进行大量的头插、头删操作。
查找
在顺序表中查找元素的时候,只需要遍历顺序表即可,找到了就返回下标,没找到返回-1。-1不是合法的下标,当返回-1的时候,就表明查找的元素不存在。

在pos位置插入元素
在pos位置插入元素,只需要将pos位置以及pos位置之后的元素都向后移动一个位置,然后将pos位置的值覆盖即可。插入元素之后不要忘记将size++。
- pos的取值必须合法,在插入数据的时候,pos可以是最后一个有效元素的下一个位置。

删除pos位置的值
删除pos位置的值只需要将pos之后的有效元素都向前挪动一个位置,然后将size--即可。
- 删除pos位置的值的时候也需要注意pos的取值,此时,pos的取值不能是有效元素的下一个位置。
修改
进行修改操作时,我们只需要将指定位置的值修改即可。
值得一提的是,直接使用赋值语句就能修改,为什么还需要封装成函数呢?
- 因为我们封装的函数有严格的边界检查。
- 直接赋值使用的 [ ] 并没有严格的边界检查,[ ] 的下标检查是一种抽查机制,不能保证准确的发现越界问题。

3.完整代码附录
SeqList.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>// 定义动态顺序表
typedef int SLDataType;typedef struct SeqList
{SLDataType* a; // 指向动态开辟的数组空间int size; // 存储有效数据个数int capacity; // 空间大小
}SL;// 初始化
void SLInit(SL* ps);
// 销毁
void SLDestroy(SL* ps);
// 打印
void SLPrint(SL* ps);
// 容量检查
void SLCheckCapacity(SL* ps);// 头插
void SLPushBack(SL* ps, SLDataType x);
// 头删
void SLPopBack(SL* ps);
// 尾插
void SLPushFront(SL* ps, SLDataType x);
// 尾删
void SLPopFront(SL* ps);// 返回下标,没有找打返回-1
int SLFind(SL* ps, SLDataType x);// 在pos位置插入x
void SLInsert(SL* ps, int pos, SLDataType x);// 删除pos位置的值
void SLErase(SL* ps, int pos);// 修改pos位置的元素
void SLModify(SL* ps, int pos, SLDataType x);
SeqList.c
#include"SeqList.h"// 初始化
void SLInit(SL* ps)
{assert(ps);ps->a = (SLDataType*)malloc(sizeof(SLDataType)*4);if (ps->a == NULL){perror("malloc failed");exit(-1);}ps->size = 0;ps->capacity = 4;
}// 销毁
void SLDestroy(SL* ps)
{assert(ps);free(ps->a);ps->a = NULL;ps->capacity = ps->size = 0;
}// 打印
void SLPrint(SL* ps)
{assert(ps);for (int i = 0; i < ps->size; i++){printf("%d ", ps->a[i]);}printf("\n");
}// 容量检查
void SLCheckCapacity(SL* ps)
{assert(ps);if (ps->size == ps->capacity){SLDataType* tmp = (SLDataType*)realloc(ps->a, ps->capacity * 2 * (sizeof(SLDataType)));if (tmp == NULL){perror("realloc failed");exit(-1);}ps->a = tmp;ps->capacity *= 2;}
}// 尾插
void SLPushBack(SL* ps, SLDataType x)
{assert(ps);SLCheckCapacity(ps);ps->a[ps->size] = x;ps->size++;
}// 尾删
void SLPopBack(SL* ps)
{assert(ps);assert(ps->size > 0);ps->size--;
}// 头插
void SLPushFront(SL* ps, SLDataType x)
{assert(ps);SLCheckCapacity(ps);// 挪动数据int end = ps->size - 1;while (end >= 0){ps->a[end + 1] = ps->a[end];--end;}ps->a[0] = x;ps->size++;
}// 头删
void SLPopFront(SL* ps)
{assert(ps);assert(ps->size > 0);int begin = 1;while (begin < ps->size){ps->a[begin - 1] = ps->a[begin];++begin;}ps->size--;
}// 查找
int SLFind(SL* ps, SLDataType x)
{assert(ps);for (int i = 0; i < ps->size; i++){if (ps->a[i] == x){return i;}}return -1;
}// 在pos位置插入x
void SLInsert(SL* ps, int pos, SLDataType x)
{assert(ps);assert(pos >= 0 && pos <= ps->size);SLCheckCapacity(ps);int end = ps->size - 1;while (end >= pos){ps->a[end + 1] = ps->a[end];--end;}ps->a[pos] = x;ps->size++;
}// 删除pos位置的值
void SLErase(SL* ps, int pos)
{assert(ps);assert(pos >= 0 && pos < ps->size);int begin = pos + 1;while (begin < ps->size){ps->a[begin - 1] = ps->a[begin];++begin;}ps->size--;
}// 修改
void SLModify(SL* ps, int pos, SLDataType x)
{assert(ps);assert(pos >= 0 && pos < ps->size);ps->a[pos] = x;
}
相关文章:
手撕数据结构 —— 顺序表(C语言讲解)
目录 1.顺序表简介 什么是顺序表 顺序表的分类 2.顺序表的实现 SeqList.h中接口总览 具体实现 顺序表的定义 顺序表的初始化 顺序表的销毁 打印顺序表 编辑 检查顺序表的容量 尾插 尾删 编辑 头插 头删 查找 在pos位置插入元素 删除pos位置的值 …...
女友学习前端第二天-笔记
2024/10/8笔记 表格 table 表格 tr 行 td 单元格内容 th 表头 第一行相当于h1 alignleft /center /right 对齐方式 应在table边上 比如<table alignleft> border 代表边框 也应在table边上 比如<table alignleft border"1"> cellpadding 单元外框与…...
电脑手机下载小米xiaomi redmi刷机包太慢 解决办法
文章目录 修改前下载速度修改后下载速度修改方法(修改host) 修改前下载速度 一开始笔者以为是迅雷没开会员的问题,在淘宝上买了一个临时会员后下载速度依然最高才100KB/s 修改后下载速度 修改方法(修改host) host文…...
Python中的策略模式:解锁编程的新维度
引言 策略模式是一种行为型设计模式,允许算法独立于使用它的客户端而变化。这使得我们可以根据不同的情况选择不同的算法或策略来解决问题,从而增强系统的灵活性。在日常开发中,策略模式常用于处理多种算法或行为之间的切换,比如…...
ara::core::Future::then()的概念和使用方法
1. 概念 在ara::core::Future的上下文中,then()是一种用于处理异步操作结果的机制。一个Future代表一个尚未完成的异步计算,它最终会产生一个结果或者一个错误。then()方法允许你在Future完成时注册一个回调函数(或者说后续操作)…...
九、5 USART串口数据包
数据包作用:把一个个单独的数据给打包起来,将同一批的数据进行打包和分割,方便接收方进行识别,方便我们进行多字节的数据通信。 1、串口收发HEX数据包 (1)数据包的格式是个人规定的,如以FF为包…...
SQL第12课——联结表
三点:什么是联结?为什么使用联结?如何编写使用联结的select语句 12.1 联结 SQL最强大的功能之一就是能在数据查询的执行中联结(join)表。联结是利用SQL的select能执行的最重要的操作。 在使用联结前,需要了解关系表…...
CentOS7 虚拟机操作系统安装及相关配置教程
1、安装虚拟机 在VMware《主页》界面中点击《创建新的虚拟机》按钮: 选择你准备好的ISO文件,点击下一步: 然后填写虚拟机的名称以及虚拟机将来保存的位置: 再次下一步,填写虚拟机磁盘大小: 继续下一步&…...
『网络游戏』窗口基类【06】
创建脚本:WindowRoot.cs 编写脚本: 修改脚本:LoginWnd.cs 修改脚本:LoadingWnd.cs 修改脚本:ResSvc.cs 修改脚本:LoginSys.cs 运行项目 - 功能不变 本章结束...
04_23 种设计模式之《单例模式》
文章目录 一、单例模式基础知识单例模式有 3 个特点: 单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式通常用于管理共享资源,如…...
视频加字幕用什么软件最快?12款工具快速添加字幕!
对于大多数同学来讲,剪辑中比较头疼的就是如何给视频加字幕和唱词啦,特别是用Pr或者FCXP等专业剪辑软件,加字幕也是特别费时的,哪怕是有批量添加的功能orz... 虽然关于这方面的内容已经很多啦,但是真正全面的内容还特…...
C++:string (用法篇)
文章目录 前言一、string 是什么?二、C语法补充1. auto2. 范围for 三、string类对象的常见构造1. Construct string object2. String destructor3. operator 四、string迭代器相关1. begin与end1)begin2)end3)使用 2. rbegin 与 r…...
力扣随机题
最接近原点的K个点 题目 973. 最接近原点的 K 个点 - 力扣(LeetCode) 思路 这就是一道排序题,直接根据公式排序,然后返回对应范围的数组就行了 代码 public int[][] kClosest(int[][] points, int k) {Arrays.sort(points, n…...
CSS样式基础样式选择器(案例+代码实现+效果图)
目录 1.css样式的规则 2.引入css样式的方式 1)行内式 2)内嵌式 3)外链式 1-link导入 2-import导入 4)总 3.css基础选择器 1)标签选择器 案例:使用标签选择器编写一个圆 1.代码 2.效果 2)类选择器 案例:使用类选择器为div添加背景色 1.代码 2.效果 3)id…...
Linux系统编程—I/O缓冲区(C语言实现)
I/O缓冲区 进程的I/O缓冲区机制是计算机操作系统中一个重要的概念,它涉及到数据在内存和外设之间的传输。以下是关于进程的I/O缓冲区机制的详细解释: 1.定义与作用 定义:I/O缓冲区是指在内存里开辟的一块区域,用来存放接收用户输…...
MySQL多表查询:行子查询
先看我的表数据 dept表 emp表 行子查询 子查询返回的结果是一行(可以是多列), 这种子查询称为行子查询 常用的操作符: , <>, IN, NOT IN 例子1. 查询与“张无忌” 的薪资及直属领导相同的员工信息 拆解成两个问题 a. 查询"张无忌"…...
.NET CORE程序发布IIS后报错误 500.19
发布IIS后浏览时报错误500.19,同时配置文件web.config的路径中也存在问号“?”。 可能原因:没有安装运行时...
Qt 6 相比 Qt 5 的主要提升与更新
自从 Qt 6 发布以来,作为 Qt 框架的一个重大版本更新,它在多个核心方面进行了深度优化和改进。与 Qt 5 相比,Qt 6 不仅提升了性能,还改进了对现代硬件和图形 API 的支持,并增强了开发者的工作流程。本文将详细介绍 Qt …...
【数据结构】介绍
介绍数据结构 数据结构是计算机科学中重要的概念,是指组织和管理数据的方式。它涉及到数据的存储、操作和访问等操作。数据结构可以分为线性结构、树形结构和图形结构等。 线性结构是最简单的数据结构之一(本玄也是这样觉得(* ̄▽ ̄*))&#…...
论医疗类系统全国运营推广策略
一、线上推广 搜索引擎优化(SEO)- 重点策略 持续更新网站内容,包括系统功能介绍、成功案例、行业新闻等,提高网站的权重和流量。进行搜索引擎优化(SEO),确定与医疗机构辅助系统相关的关键词&a…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
