【玩转408数据结构】线性表——线性表的顺序表示(顺序表)
知识回顾
通过前文,我们了解到线性表是具有相同数据类型的有限个数据元素序列;并且,线性表只是一种逻辑结构,其不同存储形式所展现出的也略有不同,那么今天我们来了解一下线性表的顺序存储——顺序表。
顺序表的定义
顺序表指的是将逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。所以顺序表的特点就是其逻辑顺序与其物理顺序相同。
我们不妨将设线性表L存储的起始位置为LOC(A),那么其顺序表L相对应的顺序存储如图所示:(这里sizeof是计算括号内数据元素所占用存储空间的大小)
通过图我们也不难观察出其顺序表的特点。这里每个数据元素的存储位置都与线性表的起始位置相差该数据元素的位序个(n个)数据元素内存大小。所以我们的顺序存储结构是随机存取的存储结构。在接下来高级程序设计语言的实现中,我们决定使用数组来实现该内容(不过需要注意的是,线性表中元素的位序是从1开始的而数组中的元素下标是从0开始的)。
元素类型初始化
静态分配
既然我们了解了顺序表,那么接下来我们就要尝试着去实现顺序表。首先,我们需要思考的是 我们应该怎么定义出顺序表中每个元素的类型呢?这并不是一个困难的问题,由于顺序表的特点,我们这里可以使用一个数组去存放顺序表中元素;不过仅仅使用数组是不行的,因为我们很难去判断我们顺序表存储了多少个元素(顺序表的长度);那么这时,我们就需要一个附加的值(length)去记录我们顺序表的当前长度,由于我们需要两个值同时存在,这里就需要用到我们之前C语言学习时的一个关键字(struct)了。通过我们的思考,我们就可以尝试写出顺序表中的顺序存储类型了。
#define MaxSize 50 typedef struct {int data[MaxSize]; // 定义元素 int length; //表示当前长度
}SqList;
于是我们不难写出上述的代码(需要注意的是,此时data[]为int类型,这里的int可以根据我们存储元素的类型去进行更改)这里我们使用数组去存储顺序表中的元素,使用length去记录当前的长度。
可以,使用该方法(静态分配)去分配时会出现一种问题,由于我们数组的大小和空间是固定的,我们在分配数组时,若数组的空间开的过大会导致其内存的浪费;若空间开的过小,又有可能导致空间占满,进而导致存入新数据时产生溢出、程序崩溃;这也就是我们进行静态分配的缺点。
思考:第三步的Length设为0,可不可以省略? 这当然是不可以的,如果我们没有对Length的值进行初始化,那么这个值在分配的时候将是随机的,这样就会导致长度计算的错误;当然写过一些代码的小伙伴可能会疑惑,我们平时也是没有初始化,他的值一直是0呀,这里主要是由于编译器的原因,我们使用的编译器自动的将其设为0了,但在考试中为了严谨性,还是建议将Length值进行初始化的。
既然静态分配有那么多缺点,那么我们能不能使用一个更好些的办法,去尽可能的避免这些问题呢?答案当然是可以的,这里我们可以采用动态分配。
动态分配
在动态分配中,存储数组的空间实在程序执行的过程中通过动态存储分配语句分配的,一旦该数组的空间占满,就另外开辟出一块更大的存储空间,用来替换掉之前的存储空间,这样可以有效的解决上面的问题。
#define InitSize 50 //顺序表初始长度typedef struct {int *data; // 指向动态分配数组的指针 int MaxSize,length; //分别表示最大容量和当前长度
}SqList;
在进行动态的申请和释放空间时,我们可以利用下面这些关键字:
C —— malloc、free 函数
L.data = (ElemType *) malloc (sizeof(ElemType) *InitSize) ;
C++ —— new、delete 函数
L.data = new ElemType (InitSize) ;
顺序表特点:
- 随机存储,我们可以通过首地址和元素序号在时间复杂度为O(1)内找到指定的元素。
- 其存储密度高,每个节点只需存储数据元素。
- 顺序表逻辑上相邻的元素物理上也相邻,所以插入和删除就需要移动大量的元素。
顺序表的基本操作实现
顺序表的插入
顺序表的插入操作:
ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e。
我们的实现思路主要就是,首先,判断输入的第i个位置是否合法;若不合法则插入失败,若合法则将第i个元素及其后面的元素依次向后移动一个位置,然后腾出一个空位置插入新元素e,顺序表的长度增加1,及插入成功。
//插入
bool ListInsert(SqList &L, int i, int e){ //传入顺序表 以及从第i个位置插入一个值e if(i<1 || i>L.length+1) //注:这里的i是表中的第几个元素,并非其数组下标return false ;if(L.length >= MaxSize) //表满 无法插入return false ;//后移for(int j=L.length; j>=i; j--){ //此时j表示的是位数 L.data[j] = L.data[j-1];} /*for(int j=L.length-1; j>=i-1; j--){ j表示的为数组下标 L.data[j+1] = L.data[j];}*/ L.data[i-1] = e ;L.length++;return true ;
}
思考:为什么代码中if语句中用length+1,而for语句中只用length呢?通过对代码的观察我们不难发现,这里if语句和for语句中的元素代表的含义并不相同,if语句中代表的是顺序表元素的位序而for语句中代表的是数组下标。
时间复杂度分析
最好情况:直接在表尾插入元素( i=n+1 ),元素直接后移即可,时间复杂度为O(1)。
最坏情况:在表头插入元素( i=1 ),元素需要后移n次,时间复杂度为O(n)。
平均情况:假设为在第 i 个位置上插入一个结点的概率,则在一个长度为n的线性表中插入一个结点时,需要移动节点的平均次数为:
因此,顺序表插入算法的时间复杂度为O(n)。
顺序表的删除
顺序表的删除操作:
ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
删除元素我们主要的实现思路就是,我们在删除第i个位置之后,需要将其后面的位置全部向前移动一位,这样就可以完成删除操作了。
//删除
bool ListDelete(SqList &L, int i, int &e){if(i<1 || i>L.length+1)return false ;e = L.data[i-1] ; //第i个元素 在数组的i-1for(int j=i; j<L.length; j++){L.data[j-1] = L.data[j] ;} L.length = L.length-1 ;return true ;}
时间复杂度分析
最好情况:直接在表尾删除元素( i=n+1 ),元素删除即可,时间复杂度为O(1)。
最坏情况:在表头删除元素( i=1 ),元素需要前移n次,时间复杂度为O(n)。
平均情况:假设为在第 i 个位置上删除一个结点的概率,则在一个长度为n的线性表中删除一个结点时,需要移动节点的平均次数为:
因此,顺序表插入算法的时间复杂度为O(n)。
由此可见,插入操作删除操作的时间主要消耗在移动元素上,而移动元素的个数与我们插入或者删除元素的位置有关,不同的插入删除位置所移动的元素个数是不同的。
顺序表的查找
按位查找
GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。
对于按位查找,由于我们的数组下标可以很好的表示出元素的顺序,这里我们就可以直接利用数组下标与元素位序的映射关系去完成返回第i个元素的值操作。
//查找第i个位置的元素值
int GetElem(SqList L, int i) {return L.data[i-1]; //数组下标从0开始
}
时间复杂度分析
由于是直接返回数组值的,所以不需要什么中间的计算,其时间复杂度是稳定的 O(1) 。
按值查找
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
对于按值查找,我们可以使用循坏,去遍历一遍我们的顺序表,这样就可以找到需要返回的值了;如果遍历一遍之后仍没有发现需要查找的值,那么就返回false,证明查找失败。
//查找
//查找第一个是e的元素 返回其位序
int LocateElem(SqList &L, int e){for(int i=0; i<L.length; i++){ //i为数组下标 if(L.data[i] == e)return i+1 ;}return 0;
}
时间复杂度分析
最好情况:查找的元素在表头,只需要查找一次即可,时间复杂度为O(1)。
最坏情况:查找的元素不存在或者在表尾,需要查找n次,时间复杂度为O(n)。
平均情况:假设为查找元素在第 i 个位置上结点的概率,则在一个长度为n的线性表中查找一个结点时,需要比较节点的平均次数为:
因此,顺序表按值查找算法的时间复杂度为O(n)。
到这里,顺序表的功能也基本完成了,当然对于这些操作,我们动态分配和静态分配的操作代码相差并不大,只是动态分配时需要多出一个增加数组长度的函数,这里在下面的完整代码展示中会体现出来,本文就不做过多描述。
顺序表完整代码
静态分配代码
//2.2 顺序表
#include<bits/stdc++.h>
#define MaxSize 50 using namespace std;typedef struct {int data[MaxSize]; // 定义元素 int length; //表示当前长度
}SqList;int ex = -1 ;//插入
bool ListInsert(SqList &L, int i, int e){ //传入顺序表 以及从第i个位置插入一个值e if(i<1 || i>L.length+1) //注:这里的i是表中的第几个元素,并非其数组下标return false ;if(L.length >= MaxSize) //表满 无法插入return false ;//后移for(int j=L.length; j>=i; j--){ //此时j表示的是位数 L.data[j] = L.data[j-1];} /*for(int j=L.length-1; j>=i-1; j--){ j表示的为数组下标 L.data[j+1] = L.data[j];}*/ L.data[i-1] = e ;L.length++;return true ;
}//删除
bool ListDelete(SqList &L, int i, int &e){if(i<1 || i>L.length+1)return false ;e = L.data[i-1] ; //第i个元素 在数组的i-1for(int j=i; j<L.length; j++){ L.data[j-1] = L.data[j] ;} L.length = L.length-1 ;return true ;} //查找
//查找第一个是e的元素 返回其位序
int LocateElem(SqList &L, int e){for(int i=0; i<L.length; i++){ //i为数组下标 if(L.data[i] == e)return i+1 ;}return 0;
}//查找第i个位置的元素值
int GetElem(SqList L, int i) {return L.data[i-1]; //数组下标从0开始
} int main(){SqList L ;for(int i=0; i<=5; i++){L.data[i] = i+1 ;}L.length = 6 ;for(int i=0; i<=L.length-1; i++) cout << L.data[i] << " " ;cout << endl;ListInsert(L, 3, 3) ;for(int i=0; i<=L.length-1; i++) cout << L.data[i] << " " ;cout << endl;ListDelete(L, 3, ex) ;cout << ex << endl ;for(int i=0; i<=L.length-1; i++) cout << L.data[i] << " " ;cout << endl;cout << GetElem(L, 3) << endl ; cout << LocateElem(L, 3) << endl;return 0;
}
动态分配代码
//2.2 顺序表
#include<bits/stdc++.h>
#define InitSize 50 //顺序表初始长度using namespace std;typedef struct {int *data; // 指向动态分配数组的指针 int MaxSize,length; //分别表示最大容量和当前长度
}SqList;int ex = -1 ;//初始化
void InitList(SqList &L) {L.data = (int *)malloc(sizeof(int));L.length = 0;L.MaxSize = InitSize;
} //动态增长数组
void IncreaseSize(SqList &L, int len) { //len为需要增加长度 int *p = L.data; //p记录之前数组地址 方便后期释放 L.data = (int *)malloc(sizeof(int) * (L.MaxSize+len)) ; //申请一片新的区域 for(int i=0; i<L.length; i++) {L.data[i] = p[i]; //将值复制到新的区域 }L.MaxSize = L.MaxSize+len; //更新最大的容量 free(p); //释放之前动态申请的空间
} //插入
bool ListInsert(SqList &L, int i, int e){ //传入顺序表 以及从第i个位置插入一个值e if(i<1 || i>L.length+1) //注:这里的i是表中的第几个元素,并非其数组下标return false ;if(L.length >= L.MaxSize) //表满 无法插入return false ;//后移for(int j=L.length; j>=i; j--){ //此时j表示的是位数 L.data[j] = L.data[j-1];} /*for(int j=L.length-1; j>=i-1; j--){ j表示的为数组下标 L.data[j+1] = L.data[j];}*/ L.data[i-1] = e ;L.length++;return true ;
}//删除
bool ListDelete(SqList &L, int i, int &e){if(i<1 || i>L.length+1)return false ;e = L.data[i-1] ; //第i个元素 在数组的i-1for(int j=i; j<L.length; j++){ L.data[j-1] = L.data[j] ;} L.length = L.length-1 ;return true ;} //查找
//查找第一个是e的元素 返回其位序
int LocateElem(SqList &L, int e){for(int i=0; i<L.length; i++){ //i为数组下标 if(L.data[i] == e)return i+1 ;}return 0;
}//查找第i个位置的元素值
int GetElem(SqList L, int i) {return L.data[i-1]; //数组下标从0开始
} int main(){SqList L ;InitList(L) ;IncreaseSize(L, 10); for(int i=0; i<=5; i++){L.data[i] = i+1 ;}L.length = 6 ;for(int i=0; i<=L.length-1; i++) cout << L.data[i] << " " ;cout << endl;ListInsert(L, 3, 3) ;for(int i=0; i<=L.length-1; i++) cout << L.data[i] << " " ;cout << endl;ListDelete(L, 3, ex) ;cout << ex << endl ;for(int i=0; i<=L.length-1; i++) cout << L.data[i] << " " ;cout << endl;cout << GetElem(L, 3) << endl ; cout << LocateElem(L, 3) << endl;return 0;
}
两个完整代码的内容大同小异,主要就是在顺序表定义初始化时会产生些许不同,我们主要理解其产生逻辑即可,其代码的运行结果图如下:
由代码可知,我们对其顺序表初始化为(1,2,3,4,5,6)就是我们第一行所展示的数字;之后我们在第三个位置插入3,所以第二行展示的就是插入后的结果;第三行则是输出我们在删除时需要删除的位置,紧接着我们将第三个位置的数字删除,所以第四行显示的是其删除后的结果;最后两行就是输出的为第三个位置和查找值为3的元素在第几个位置并输出。
顺序表的内容到这里也就结束了,我们在下面尽量可以独立的去实现一下代码,这样可以更好的帮助我们理清其内部的逻辑。
相关文章:

【玩转408数据结构】线性表——线性表的顺序表示(顺序表)
知识回顾 通过前文,我们了解到线性表是具有相同数据类型的有限个数据元素序列;并且,线性表只是一种逻辑结构,其不同存储形式所展现出的也略有不同,那么今天我们来了解一下线性表的顺序存储——顺序表。 顺序表的定义 …...

图像处理之《黑盒扰动的可逆噪声流鲁棒水印》论文阅读
一、文章摘要 近年来,基于深度学习的数字水印框架得到了广泛的研究。现有的方法大多采用基于“编码器-噪声层-解码器”的架构,其中嵌入和提取过程分别由编码器和解码器完成。然而,这种框架的一个潜在缺点是编码器和解码器可能不能很好地耦合…...

一个Vivado仿真问题的debug
我最近在看Synopsys的MPHY仿真代码,想以此为参考写个能实现PWM-G1功能的MPHY,并应用于ProFPGA原型验证平台。我从中抽取了一部分代码,用Vivado自带的仿真器进行仿真,然后就遇到了一个莫名其妙的问题,谨以此文作为debug…...

C#阿里云消息列队推送消息
推送消息到队列 IMNS nativeclient new Aliyun.MNS.MNSClient(accessKeyId, accessKeySecret, endpoint, _stsToken);var nativeSend nativeclient.GetNativeTopic("SMQ");nativeSend.PublishMessage("推送消息内容"); 需要引用Aliyun.MNS.dll 下载地址…...

Stable Diffusion 模型下载:majicMIX sombre 麦橘唯美
本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十...

WindowsLinuxmeterepreter渗透命令回顾
最近小编发现在学红队的时候总会忘记一些命令(基础的),导致整天红温,于是今天就来偷个懒记一下(一起回顾一下) 1.Linux 1.查看当前按目录 pwd2.查看文件内容 cat filename.txt3.cd 家族 cd ..|| cd ../…...

KingSCADA实现按钮点击效果
哈喽,你好啊,我是雷工! 在做SCADA项目的时候,按钮是不可缺少的功能,但软件自带的按钮太丑,已经无法满足现如今客户对界面美观度的要求。 这时候就需要UI小姐姐设计美观大气的SCADA界面,但UI设计…...

Python编程-二万字浅谈装饰器原理与装饰器设计模式和函数式编程案例讲解
Python编程-浅析装饰器原理与装饰器设计模式和函数式编程案例讲解 本文制作时基于Python3.11.8与Python3.12.1,存在谬误,请联系修改,希望对你有所帮助 什么是函数式编程 函数式编程(Functional Programming)是一种编程…...

基于Zigbee的智能温室大棚系统(附详细使用教程+完整代码+原理图+完整课设报告)
🎊项目专栏:【Zigbee课程设计系列文章】(附详细使用教程+完整代码+原理图+完整课设报告) 前言 👑由于无线传感器网络(也即是Zigbee)作为🌐物联网工程的一门必修专业课,具有很强的实用性,因此很多院校都开设了zigbee的实训课程;👑同时最近很多使用了我的单片机课…...

【Web】Redis未授权访问漏洞学习笔记
目录 简介 靶机配置 Redis持久化 Redis动态修改配置 webshell 反弹shell Redis写入反弹shell任务 加固方案 简介 Redis(Remote Dictionary Server 远程字典服务器)是一个开源的内存数据库,也被称为数据结构服务器,它支持…...

【JAVA WEB】 css背景属性 圆角矩形的绘制
目录 背景属性设置 圆角矩形 背景属性设置 背景颜色,在style中 background-color:颜色; 背景图片 background-image:url(……) 背景图片的平铺方式 background-repeat: 平铺方式 repeat 平铺(默认)no-repeat 不平铺repeat-x 水平平铺repea…...

Docker-现代化应用部署的利器
一、容器部署的发展 今天我们来说说容器部署。我们知道容器部署的发展大致分三个阶段,下面来介绍一下不同阶段的部署方式的优缺点 物理机部署 优点是可以提供更高的性能、资源控制,也可以提供更好的数据隔离和安全性,因为不同的应用程序运行在…...

「优选算法」:山脉数组的峰顶索引
一、题目 符合下列属性的数组 arr 称为 山脉数组 : arr.length > 3存在 i(0 < i < arr.length - 1)使得: arr[0] < arr[1] < ... arr[i-1] < arr[i] arr[i] > arr[i1] > ... > arr[arr.length - 1] …...

网络安全红队基础建设与介绍
1.ATT&CK相关背景 ATT&CK在各种日常环境中都很有价值。开展任何防御活动时,可以应用ATT&CK防御法,参考攻击者及其行为。ATT&CK不仅对网络防御者提供通用技术库,还为渗透测试和红队提供了基础。提到对抗行为时,这为…...

Java语法学习反射
Java语法学习反射 大纲 基本介绍class的介绍 具体案例 1. 基本介绍 流程图(程序在计算机的阶段) 反射的主要的类 这个提高效率不大 2. class的介绍 对于第三点:首先类只会加载一次,得到的class的对象,也只有一…...

【MySQL】操作库 —— 库的操作 -- 详解
一、增删数据库 1、创建数据库 create database db_name; 本质就是在 /var/lib/mysql 创建一个目录。 说明: 大写的表示关键字。[ ] 是可选项。CHARACTER SET:指定数据库采用的字符集。COLLATE:指定数据库字符集的校验规则。 2、数据库删除…...

Rust安装——Win10
安装步骤 1、下载RUSTUP-INIT.EXE(64-BIT) 2、由于国外源下载依赖太慢,因此建议增加win10环境变量配置国内源,增加RUSTUP_DIST_SERVER、RUSTUP_UPDATE_ROOT环境变量即可 RUSTUP_DIST_SERVER随便选择其中的一个源就行,…...

【教学类-46-07】20240212立体春字1.0
背景需求: 在南浔古镇的非遗文化馆里看到一个新年活动折纸——立体春字, 我记得这个就是一个双三角结构折纸,完全可以用15*15的手工纸给孩子们做一套。 折纸教程 双三角折法 【“鼠”你有才】纸艺教学 剪纸——立体春字(2月23日…...

Python语言例题集(003)
#!/usr/bin/python3 #猜数字 import random secretNumberrandom.randint(1,20) print(‘我想了一个1到20间的整数,你能猜出来吗?’) for guessesTaken in range(1,7): print(‘猜一下!’) guessint(input()) if guess<secretNumber: pr…...

UE5 播放本地MP3、MP4
1.创建一个媒体播放器 2.如创建视频,勾选。 它会多一个媒体纹理给你 3.1 设置音频 在一个actor上添加“媒体音频组件” “音频媒体播放器”赋值给它 3.2播放音频 添加一个音频媒体播放器变量, 赋值 地址使用绝对地址 4.1设置视频 UI上创建一个imag…...

NLP_“预训练+微调大模型”模式和Prompt/Instruct模式的异同
文章目录 “预训练微调大模型”的模式以提示/指令模式直接使用大模型“预训练微调大模型”模式和Prompt/Instruct模式的异同小结 “预训练微调大模型”的模式 经过预训练的大模型所习得的语义信息和所蕴含的语言知识,很容易向下游任务迁移。NLP应用人员可以根据自己…...

普通人应该如何使用GPT
现在GPT4推出的GPTs,包含了各个行业方向,比如DALL(绘图)、Diagrams(图标、流程图)、KAYAK(航旅助手)、Murder Mystery Mayhem(侦探扮演)、Canva(设…...

pycharm像jupyter一样在控制台查看后台变量
更新下:这个一劳永逸不用一个一个改 https://blog.csdn.net/Onlyone_1314/article/details/109347481 右上角运行...

Ansible command命令模块 这个模块可以直接在远程主机上执行命令,并将结果返回本主机。
目录 参数介绍练习环境配置主机清单配置无密码链接ping模块 command 命令模块也可以用来安装点东西看个路径 command 指定目录来 指定命令 参数介绍 chdir # 在执行命令之前,先切换到该目录 executable # 切换shell来执行命令,需要使用命令的绝对…...

C语言-3
定义指针 /*指针的概念:1.为了方便访问内存中的内容,给每一个内存单元,进行编号,那么我们称这个编号为地址,也就是指针。2.指针也是一种数据类型,指针变量有自己的内存,里面存储的是地址,也就是…...

Quartus工程的qsf配置约束文件介绍
一、qsf文件概述 qsf:Quartus Setting File,是Quartus工程的配置文件; 包含一个Quartus工程的所有约束,包括工程的软件版本信息、FPGA器件信息、引脚约分配、引脚电平分配,编译约束和用于Classic TimingAnalyzer的时…...

【网工】华为设备命令学习(Telnet)
本次实验AR3为我们实际中远程的路由,AR4模拟我们的设备,最终实现Telnet的远程控制路由! 本次笔记主要记录Telnet技术实现原理,后续再补充具体配置代码。 Telnet协议是TCP/IP协议族中的一员,是Internet远程登录服务的…...

搜索专项---最短路模型
文章目录 迷宫问题武士风度的牛抓住那头牛 一、迷宫问题OJ链接 本题思路:只需要记录各个点是有哪个点走过来的,就能递推得出路径。记录前驱假设从 1,1 这个点向下走到了2, 1,则将2,1这个点的前驱记为1,1。这样,将整张地图 bfs 后,…...

安装PostgreSQL和PostGIS
安装环境 Windows 2019 Standard Server 安装PostgreSQL 安装PostgreSQL 16 安装PostGIS 用PostgreSQL 16对应的PostGIS https://download.osgeo.org/postgis/windows/pg16/ https://download.osgeo.org/postgis/windows/pg16/postgis-bundle-pg16x64-setup-3.4.1-1.exe 创建…...

MySQL-----DCL基础操作
▶ DCL简介 DCL英文全称是Data ControlLanguage(数据控制语言),用来管理数据库用户、控制数据库的访问权限。 DCL--管理用户 ▶ 查询用户 use mysql; select * from user; ▶ 创建用户 ▶ 语法 create user 用户名主机名 identified by 密码 设置为在任意主机上访问…...