实现顺序表(增、删、查、改)

引言:顺序表是数据结构中的一种形式,就是存储数据的一种结构。

这里会用到动态内存开辟,指针和结构体的知识

1.什么是数据结构

数据结构就是组织和存储数据的结构。

数据结构的特性:

物理结构:在内存中存储的数据是否连续

逻辑结构:   逻辑上是否连续


2.什么是顺序表

为了更好的实现顺序表,首先的了解其的特性。

顺序表的特性:

物理结构:连续的

逻辑结构:连续的

顺序表也是线性结构的一种

线性结构的特性:

物理结构:不一定连续

逻辑结构:连续的

顺序表的底层是数组。顺序表在数组的基础上能通过调用函数实现数据的增删查改。

数组在内存中开辟的空间是连续的,所以其存储的数据在内存中也是连续的。


3.静态顺序表与动态顺序表哪个更好呢? 

顺序表又分为静态顺序表,和动态顺序表

那么哪种更好呢? 

静态顺序表和动态顺序表的结构

//静态顺序表的结构
struct SeqList
{int arr[100];//开辟的空间大小已经确定int size;//记录当前元素的个数
};
//动态顺序表的结构
struct SeqList
{int* arr;int size;//记录当前元素的个数int capacity;//记录当前能存放元素的个数,如果size=capacity时,扩容
};

动态顺序表的结构图解:

 

比较:

静态顺序表开辟的存放数据的空间:如果开辟过大,则浪费空间;若开辟过小,若想存的数据过多,则有的数据存不进去。

而动态顺序表则更灵活,不够了再开辟就可以了。

所以动态顺序表更好,这里也只会将动态顺序表的实现。


4.动态顺序表的实现

4.1通过多文件的形式实现

文件管理:

test.c进行各个接口的测试
SeqList.c实现增删查改函数的实现
SeqLst.h引用需要使用的头文件,动态顺序表结构体的定义,实现增删查改函数函数的声明,#define 定义的符号

需要实现的函数: 

初始化、销毁创建完动态顺序表的结构后应对其成员进行初始化,释放开辟的空间,并将结构体的成员初始化

有头插,尾插

任意插

头删,尾删

任意删

查找顺序表中是否有查找的元素

这里直接给出完整的SeqList.h 中的全部代码:

#pragma once//防止头文件多次包含//用到的库函数
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>//如果以后想存储的数据不再是int 只需要改这一出的int改为别的数据类型
#define SLDataType int//顺序表的结构定义
typedef struct SeqList
{SLDataType* arr;int size;int capacity;
}SL;//以下为需要实现的函数的声明
//初始化
void SLInit(SL* ps);
void SLDestory(SL* ps);
//打印
void SLPrint(SL ps);
//尾插,尾删
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);
//尾删,头删
void SLPopFront(SL* ps);
void SLPopBack(SL* ps);
//任意插,任意删
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
//查找
int SLFind(SL* ps,SLDataType find);

4.2初始化和销毁注意事项

//初始化
void SLInit(SL* ps)
{ps->arr = NULL;ps->size = 0;ps->capacity = 0;
}

 一定要传定义的结构体的地址,这样才能改变所定义的结构体的值。

//销毁
void SLDestory(SL* ps)
{assert(ps);free(ps->arr);ps->arr = NULL;ps->size = 0;ps->capacity = 0;
}

4.3尾插,头插中的一些注意事项

尾插实现函数:

void SLPushBack(SL* ps, SLData x)
{//ps不能为NULLassert(ps != NULL);//增容:1.capacity和size都为0   2.空间不够用if (ps->capacity == ps->size){int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;SLData* tmp = (SLData*)realloc(ps->arr, newcapacity * sizeof(SLData));if (tmp == NULL){perror("realloc");exit(-1);}//开辟成功ps->arr = tmp;ps->capacity = newcapacity;}//将值写入顺序表ps->arr[ps->size] = x;//插入完顺序表中元素的个数+1ps->size++;
}

增容问题:

如果结构体中的size 和capacity 都为0,或size等于capacity(所开辟的空间都被使用完)时,就要增容。

用realloc,malloc还是用calloc开辟空间呢?

考虑到增容的问题所以用realloc开辟空间。

那么增多大呢?

这里不好解释,建议增大当前所开辟空间的2或3倍。如果开辟小了的话,很快就会被用完,就会频繁地开辟,如果是下图中realloc开辟空间中的情况2,拷贝数据很影响程序性能;若开辟过大,则浪费空间。

因为插入会考虑到增容问题所以可以写一个实现增容的函数

void CheckSLCapacity(SL* ps)
{//如果size等于capacity说明开辟的空间已经使用完,得增容if (ps->capacity == ps->size){int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;SLData* tmp = (SLData*)realloc(ps->arr, newcapacity * sizeof(SLData));if (tmp == NULL){perror("realloc");exit(-1);}//开辟成功ps->arr = tmp;ps->capacity = newcapacity;}
}

头插和尾插实现的代码:

//头插
void SLPushFront(SL* ps, SLData x)
{assert(ps != NULL);CheckSLCapacity(ps);//增容函数,上面的尾插的代码中的实现增容的代码也可以替换为这个函数for (int i = ps->size; i>0; i--){ps->arr[i] = ps->arr[i - 1];}ps->arr[0] = x;ps->size++;
}//头插
void SLPushBack(SL* ps, SLDataType x)
{assert(ps != NULL);CheckSLCapacity(ps);ps->arr[ps->size] = x;ps->size++;
}

 4.4 头删和尾删

实现代码:

//尾删
void SLPopBack(SL* ps)
{assert(ps != NULL);assert(ps->size > 0);ps->size--;
}//头删
void SLPopFront(SL* ps)
{assert(ps != NULL);assert(ps->size > 0);for (int i = 0; i <ps->size-1 ; i++)ps->arr[i] = ps->arr[i + 1];ps->size--;
}

注意:如果顺序表中没有数据那么就不能继续删除了, assert(ps->size > 0)防止没有数据了继续删。

尾删:只需要将size--就可以,不需要将最后的数据修改(修改也是可以的),但别忘了最后size--

头删:将除头部的其他数据整体向前挪动一位。

头删图解:

4.5 任意删和任意插

实现代码:

//任意插
//pos为想要插入的位置的下标
void SLInsert(SL* ps, int pos, SLDataType x)
{assert(ps);//若pos小于0,不能插入;若想要插入的位置大于size也不能插入assert(pos >= 0 && pos <= ps->size);//考虑扩容问题CheckSLCapacity(ps);//注意赋值的顺序和 i 的范围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--;}

注意事项:

任意插入:

对插入位置下标(pos)的限制: pos >= 0 && pos <= ps->size

图解:

通过上图可以用任意插函数实现头插和尾插

 

任意插入后挪动数据的方式:

从pos位置开始集体往后挪

 任意删除后的数据挪动方式:

从size-1 开始往前挪直到pos+1 的位置。

用任意插入和任意删除的函数实现 头插,尾插和头删,尾删

//头插、尾插
void SLPushBack1(SL* ps, SLDataType x)
{SLInsert(ps, ps->size, x);
}
void SLPushFront1(SL* ps, SLDataType x)
{SLInsert(ps, 0, x);
}
//尾删,头删
void SLPopFront1(SL* ps)
{SLErase(ps, 0);
}
void SLPopBack1(SL* ps) 
{SLErase(ps, ps->size-1);}

4.6查找 

实现函数:

int SLFind(SL* ps,SLDataType find)
{assert(ps);for (int i = 0; i < ps->size; i++){//如果找到返回下标if (ps->arr[i] == find)return i;}//没找到返回小于0 的数return -1;
}

查找函数的使用:

	int find = SLFind(&s, 6);if (find < 0)printf("找不到\n");elseprintf("%d\n", find);

为什么没找到返回小于0的数?

因为存储的数据的下标都大于等于0。


 

 5.完整代码来喽!

test.c:(这就是我自己写代码使用的一些测试用例,大家可以自己写测试用例,自己测试,则一块不是很重要)

#include "SeqList.h"
void SLTest()
{SL s;SLInit(&s);//SLPrint(s);//SLPushBack(&s, 1);//SLPrint(s);//SLPushBack(&s, 2);//SLPrint(s);	//SLPushBack(&s, 3);//SLPrint(s);//SLPushBack(&s, 4);//SLPrint(s);SLPushBack(&s, 5);SLPrint(s);/*SLPushBack(NULL, 5);*/SLPushFront(&s, 6);SLPrint(s);SLPushFront(&s, 7);SLPrint(s);/*SLPushFront(NULL, 7);*/SLPopFront(&s);SLPrint(s);SLPopBack(&s);SLPrint(s);//SLPopFront(&s);//SLPrint(s);SLPopFront(&s);SLPrint(s);SLDestory(&s);
}
void SLTest1()
{SL s;SLInit(&s);SLPushFront(&s, 1);SLPushBack(&s, 2);SLPushBack(&s, 3);SLPrint(s);SLInsert(&s, 0, 4);SLPrint(s);SLInsert(&s,4,5);SLInsert(&s,3,6);SLPrint(s);SLErase(&s, 4);SLPrint(s);SLErase(&s, 0);SLPrint(s);int find = SLFind(&s, 6);if (find < 0)printf("找不到\n");elseprintf("%d\n", find);SLDestory(&s);
}
int main()
{/*SLTest();*/SLTest1();return 0;
}

SeqList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>#define SLDataType int
typedef struct SeqList
{SLDataType* arr;int size;int capacity;
}SL;//初始化
void SLInit(SL* ps);
void SLDestory(SL* ps);
//打印
void SLPrint(SL ps);
//尾插,尾删
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);
//尾删,头删
void SLPopFront(SL* ps);
void SLPopBack(SL* ps);
//任意插,任意删
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
//查找
int SLFind(SL* ps,SLDataType find);

 

SeqList.c:

#include "SeqList.h"void CheckSLCapacity(SL* ps)
{if (ps->capacity == ps->size){int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));if (tmp == NULL){perror("realloc");exit(-1);}//开辟成功ps->arr = tmp;ps->capacity = newcapacity;}
}void SLInit(SL* ps)
{ps->arr = NULL;ps->size = 0;ps->capacity = 0;
}void SLDestory(SL* ps)
{assert(ps);free(ps->arr);ps->arr = NULL;ps->size = 0;ps->capacity = 0;
}void SLPrint(SL s)
{for (int i = 0; i < s.size; i++)printf("%d ", s.arr[i]);printf("\n");
}void SLPushBack(SL* ps, SLDataType x)
{assert(ps != NULL);CheckSLCapacity(ps);ps->arr[ps->size] = x;ps->size++;
}void SLPushFront(SL* ps, SLDataType x)
{assert(ps != NULL);CheckSLCapacity(ps);for (int i = ps->size; i>0; i--){ps->arr[i] = ps->arr[i - 1];}ps->arr[0] = x;ps->size++;
}void SLPopBack(SL* ps)
{assert(ps != NULL);assert(ps->size > 0);ps->size--;
}void SLPopFront(SL* ps)
{assert(ps != NULL);assert(ps->size > 0);for (int i = 0; i <ps->size-1 ; i++)ps->arr[i] = ps->arr[i + 1];ps->size--;
}void SLInsert(SL* ps, int pos, SLDataType x)
{assert(ps);assert(pos >= 0 && pos <= ps->size);CheckSLCapacity(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 find)
{assert(ps);for (int i = 0; i < ps->size; i++){if (ps->arr[i] == find)return i;}return -1;
}

结语:

头次写数据结构,放平心态 ,一定要写完一个接口调试一个接口,不要都写完了再去调试,若有一堆问题容易心态爆炸(boom)。

 拜拜,下一期。目标项目:通讯录。

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

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

相关文章

通过vite创建项目

一、VUE3官网 Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org) 二、通过Vite创建项目 1、在cmd窗口下&#xff0c;全局安装vite //使用国内镜像源 npm config set registryhttps://registry.npmmirror.com//安装最新版vite npm install -g vitelatest Vite | 下一代…

Pygame基础11-mask 蒙版

蒙版 蒙版是二值化的图像&#xff0c;每个像素的值只能是0或1。 mask(蒙版)的用途&#xff1a; 碰撞检测部分着色 案例 和字母的碰撞检测 当玩家碰到字母 α \alpha α时&#xff0c;改变玩家颜色为绿色&#xff0c;否则为红色。 注意&#xff1a;我们希望碰到字母 α \alp…

考研数学1800还是660还是880?

24考完&#xff0c;大家都发现&#xff0c;没有一本习题册&#xff0c;覆盖了考试的所有知识点。 主流的模拟卷&#xff0c;都没有达到24卷的难度。 这就意味着&#xff1a; 一本习题册不够了&#xff01; 刷主流模拟卷不够了&#xff01; 这会需要整个考研复习的安排&…

C++(set和map详解,包含常用函数的分析)

set set是关联性容器 set的底层是在极端情况下都不会退化成单只的红黑树,也就是平衡树,本质是二叉搜索树. set的性质:set的key是不允许被修改的 使用set需要包含头文件 set<int> s;s.insert(1);s.insert(1);s.insert(1);s.insert(1);s.insert(2);s.insert(56);s.inser…

制造业工厂怎么通过MES系统来升级改造车间管理

在当今高度竞争的市场环境下&#xff0c;制造业企业需要不断提高生产效率&#xff0c;以在激烈的竞争中立于不败之地。而一种被广泛应用的方法就是利用MES控制系统&#xff0c;通过数字化管理和自动化控制来改造生产车间提升生产效率。 1、MES管理系统能够实现对生产过程的全面…

Navicat Premium 16 Mac/win---数据库设计、管理与维护轻松掌握数据库管理精髓

Navicat Premium是一款功能强大的数据库开发工具&#xff0c;支持多种数据库系统&#xff0c;如MySQL、Redis、MariaDB、Oracle等&#xff0c;并可与云数据库兼容&#xff0c;如Amazon RDS、Microsoft Azure等。它提供了直观易用的用户界面&#xff0c;使得开发者能够轻松上手并…

k8s calico由IPIP模式切换为BGP模式

按照官网calico.yaml部署后&#xff0c;默认是IPIP模式 查看route -n &#xff0c; 看到是tunl0口进行转发 怎么切换到BGP模式呢&#xff1f; kubectl edit ippool 将ipipMode由Always修改为Never &#xff0c;修改后保存文件即可。无需做任何操作&#xff0c;自动就切换为BG…

MySql实战--普通索引和唯一索引,应该怎么选择

在前面的基础篇文章中&#xff0c;我给你介绍过索引的基本概念&#xff0c;相信你已经了解了唯一索引和普通索引的区别。今天我们就继续来谈谈&#xff0c;在不同的业务场景下&#xff0c;应该选择普通索引&#xff0c;还是唯一索引&#xff1f; 假设你在维护一个市民系统&…

stm32cubeMX_io输入输出讲解

1创建项目&#xff08;可在专栏里找到&#xff09; 2进入当前页面点击引脚将弹出下图选项选择输入输出 带点击GPIO 点击引脚弹出如下选项根据需求选择 如有需要可以使用外部时钟&#xff1b;设置如图使用外部时钟 生成代码 将会弹出一个提示点击中间项//打开项目

HarmonyOS NEXT应用开发之MVVM模式

应用通过状态去渲染更新UI是程序设计中相对复杂&#xff0c;但又十分重要的&#xff0c;往往决定了应用程序的性能。程序的状态数据通常包含了数组、对象&#xff0c;或者是嵌套对象组合而成。在这些情况下&#xff0c;ArkUI采取MVVM Model View ViewModel模式&#xff0c;其…

clickhouse 源码编译部署

clickhouse 源码编译部署 版本 21.7.9.7 点击build project&#xff0c;编译工程&#xff0c;经过一定时间&#xff08;第一次编译可能几个小时&#xff0c;后续再编译&#xff0c;只编译有改动的文件&#xff09;生成release目录 在cmake-build-release → programs目录下…

vivado eFUSE 寄存器访问和编程

eFUSE 寄存器访问和编程 注释 &#xff1a; 在 MPSoC 和 Versal 器件上不支持以下 eFUSE 访问和编程方法。 7 系列、 UltraScale 和 UltraScale 器件具有一次性可编程位用于执行特定功能 &#xff0c; 称为 eFUSE 位。不同 eFUSE 位类型如 下所述&#xff1a; • …

单例(Singleton)设计模式

2.1 设计模式概述 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱&#xff0c;不同的棋局&#xff0c;我们用不同的棋谱。"套路" 经典的设计模式共有23种。每个…

语音识别:基于HMM

HMM语音识别的解码过程 从麦克风采集的输入音频波形被转换为固定尺寸的一组声学向量&#xff1a; 其中是维的语音特征向量&#xff08;例如MFCC&#xff09;。 解码器尝试去找到上述特征向量序列对应的单词&#xff08;word&#xff09;的序列&#xff1a; 单词序列的长度是。…

【大数据存储】实验4 NoSQL数据库

实验4 NoSQL数据库 NoSQL数据库的安装和使用实验环境&#xff1a; Ubuntu 22.04.3 Jdk 1.8.0_341 Hadoop 3.2.3 Hbase 2.4.17 Redis 6.0.6 mongdb 6.0.12 mogosh 2.1.0 Redis 安装redis完成 新建终端启动redisredis-server新建一个终端redis-cli 建表操作 尝…

超越传统时序!多模态+时间序列8个创新方案,刷新SOTA

传统时间序列无法有效捕捉数据中复杂的非线性关系&#xff0c;导致在处理具有复杂动力学特性的系统时效果不佳。为解决此问题&#xff0c;研究者提出了多模态时间序列。 在预测任务中&#xff0c;多模态时间序列能够整合来自不同类型数据源的信息&#xff0c;从而提供更全面的洞…

笔记: JavaSE day15 笔记

第十五天课堂笔记 数组 可变长参数★★★ 方法 : 返回值类型 方法名(参数类型 参数名 , 参数类型 … 可变长参数名){}方法体 : 变长参数 相当于一个数组一个数组最多只能有一个可变长参数, 并放到列表的最后parameter : 方法参数 数组相关算法★★ 冒泡排序 由小到大: 从前…

JavaScript(六)---【回调、异步、promise、Async】

零.前言 JavaScript(一)---【js的两种导入方式、全局作用域、函数作用域、块作用域】-CSDN博客 JavaScript(二)---【js数组、js对象、this指针】-CSDN博客 JavaScript(三)---【this指针&#xff0c;函数定义、Call、Apply、函数绑定、闭包】-CSDN博客 JavaScript(四)---【执…

并发编程之线程池的应用以及一些小细节的详细解析

线程池在实际中的使用 实际开发中&#xff0c;最常用主要还是利用ThreadPoolExecutor自定义线程池&#xff0c;可以给出一些关键的参数来自定义。 在下面的代码中可以看到&#xff0c;该线程池的最大并行线程数是5&#xff0c;线程等候区&#xff08;阻塞队列)是3&#xff0c;即…

数据挖掘|关联分析与Apriori算法详解

数据挖掘|关联分析与Apriori算法 1. 关联分析2. 关联规则相关概念2.1 项目2.2 事务2.3 项目集2.4 频繁项目集2.5 支持度2.6 置信度2.7 提升度2.8 强关联规则2.9 关联规则的分类 3. Apriori算法3.1 Apriori算法的Python实现3.2 基于mlxtend库的Apriori算法的Python实现 1. 关联分…