【数据结构】顺序表和链表
1.线性表
我们在C语言当中学过数组,其实呢,数组可以实现线性表,线性表理解上类似于数组,那么什么是线性表呢?线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。下面我们就将介绍顺序表和链表。
2.顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存 储。在数组上完成数据的增删查改。一般情况下顺序表可以分为静态顺序表和动态顺序表
- 静态顺序表:使用定长数组存储元素。
- 动态顺序表:使用动态开辟的数组存储。
静态数据表呢,其实就是给一个数组,我们对这个数组进行增删查改,只不过,数据结构的意思把这一系列的操作封装了起来,我们在使用的使用直接调用相应的接口。顺序表的静态存储相对简单,我们不在此实现。
动态顺序表的意思就是,可以根据需要及时的进行扩容,从而满足要求,我们之前实现过通讯录,其实这里和通讯录的原理是类似的,接下来我们实现相应的接口。
typedef struct SeqList
{SLDataType *a;//指向这块空间的起始地址int size;//存放的有效数据int capacity;//容量
}SL ;//通过typedef将它重命名为SL方便我们以后使用
//初始化顺序表
void SeqListInit(SL* ps);
//扩容
void SeqListCheck(SL* ps);
//打印
void SeqListPrint(SL* ps);
// 尾插尾删
void SeqListPushBack(SL* ps, SLDataType x);
void SeqListPopBack(SL* ps);
//头插头删
void SeqListPushFront(SL* ps, SLDataType x);
void SeqListPopFront(SL* ps);
//任意位置插入删除
void SeqListInsert(SL* ps, size_t pos, SLDataType x);
void SeqListErase(SL* psl, size_t pos);
这是顺序表整个工程文件的头文件,包含了结构体的声明,和各种接口的声明。结构体的定义中我们定义了一个指向我们目标空间的起始地址,这是用来指向我们所开辟的空间的,当然能不能定义一个数组呢?也是可以,但是这样就变成了静态顺序表的实现了。size的定义是为了实时的显示空间内的有效数据大小,当然一个空间是不是有它的大小,那capacity就是它的容量。接下来将对任意位置插入删除的函数进行实现。
//任意位置插入
void SeqListInsert(SL* ps, size_t pos, SLDataType x)
{assert(ps);//如果ps=NULL,则终止程序if (ps->size == ps->capacity)//扩容{SeqListCheck(ps);}int end = ps->size - 1;while (pos <= end){ps->a[end + 1] = ps->a[end];end--;}ps->a[pos] = x;ps->size++;
}
任意位置的插入,实际上是把数据整体往后挪动,最终把目标位置空出来,把目标数据放进去,注意这是从后往前挪,我们的头插也是这样的道理,事实上头插就是一种特殊的任意位置插入,因此,任意位置插入实现之后可以在头插直接调用该接口就可实现。
//任意位置删除
void SeqListErase(SL* ps, size_t pos)
{assert(ps);for (int start = pos; start < ps->size - 1; start++){ps->a[start] = ps->a[start + 1];}ps->size--;
}
任意位置的删除,实际上就是把后面的数据逐个往前挪,把目标位置的数据覆盖掉,便达到了删除的目的了,注意这是从前往后挪,头删也是这样的道理,头删可以直接调用该接口,来实现头删。
补充知识:
realloc增容是一般取2倍,为什么呢?因为如果增的越多的话,有可能空间的浪费就越多,如果增的越少,虽然空间越省,但是如果我们存放的数据相对增容是比较大的,这就面临着频繁增容的情况,这消耗代价也是蛮大的,所以我们取折中的方法增2倍。
3.链表
上面我们介绍了顺序表,但是大家敏锐的发现了问题没有,我们在任意位置插入的时候,就加入在头部插入时间复杂度是多少?是不是O(N),而且它在扩容的时候面临着扩的过大,扩的过小的问题,那有没有一种结构让我们可以不挪动其它数据直接插入,而且需要多少我们申请多少,当然也是存在这样的一种数据结构的,这就是我们的链表。
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
注意:
- 链式结构在逻辑上是连续的,但是在物理上不一定连续
- 现实中的结点一般都是从堆上申请出来的
- 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
一个数据结构,我们一般对它进行的操作就是进行增删查改,所以下面将对链表的一些接口进行实现。主要涉及以下接口:
typedef int SListDataType;
typedef struct SListNode
{SListDataType data;struct SListNode* next;
}SListNode;// 动态申请一个结点
SListNode* BuySListNode(SListDataType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SListDataType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SListDataType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SListDataType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
// 因为单链表只能找到它后面的节点,找不到它前面的节点,双链表可以
void SListInsertAfter(SListNode* pos, SListDataType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
// 删除了之后,pos后面位置的内容上哪找,会造成内存泄露
void SListEraseAfter(SListNode* pos);
这是链表整个工程文件的头文件,包含了结构体的声明,和各种接口的声明。结构体的定义中我们定义了节点的数据,以及节点所存放下一个节点的地址,这个地址就是用来指向下一个节点的,从而实现链表的逻辑连续。在链表接口声明中,下面将进行一一的实现。
// 动态申请一个结点
SListNode* BuySListNode(SListDataType x)
{SListNode* pList = (SListNode*)malloc(sizeof(SListNode));if (pList == NULL){printf("开辟新节点失败");exit(-1);}pList->data = x;pList->next = NULL;//这点很重要,如果不置为NULL,极有可能越界访问return pList;
}
上面所介绍的顺序表由于在通讯录当中已经介绍了大部分内容,所以只将部分接口进行实现,链表是一个新的知识点,因此进行详细的介绍,前面我们已经了解单链表它的一种形式,所以我们在开辟一个新节点时,要注意pList->next = NULL,不置为空,系统会随机生成一个地址,那如果我们不小心调用了就会造成越界访问。
补充知识:
链表在堆区开辟空间时,有可能缠绕在一起,为什么呢?因为堆区虽然向上生长的,但是存在下面的空间被释放掉,之后开辟在下面了。
// 单链表尾插
void SListPushBack(SListNode** pplist, SListDataType x)
{if (*pplist == NULL)//分情况,如果这种情况没有,下面cur->next就会导致对NULL访问的错误{*pplist = BuySListNode(x);}else{SListNode* cur = *pplist;while (cur->next != NULL){cur = cur->next;}cur->next = BuySListNode(x);//这里通过cur可以改外部变量的值,需要注意一下}
}
尾插整体的思路就是我们应该首先找到尾,当然这里就分了情况,如果一开始就是尾,直接插入数据,不是的话我们就需要遍历整个链表,找到尾,然后将数据插入,遍历唯一需要注意的就是cur = cur->next,这是链表的关键,所以单链表的使用就两点,一是头指针的建立与使用,二是节点中存放着下一个节点的地址。
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{if (*pplist == NULL)//分情况,如果这种情况没有,下面cur->next就会导致对NULL访问的错误{return;//空就是没有要删的,直接返回}else{SListNode* cur = *pplist;SListNode* tail = *pplist;//设置该指针变量唯一目的就是,为了是新的尾节点的next置成NULL,防止成为野指针while (cur->next != NULL){tail = cur;//最后一次执行的时候会把cur之前那个节点即最终结果的尾赋给tailcur = cur->next;}free(cur);tail->next = NULL;//别忘记置成NULL,防止对NULL造成访问}
}
尾删的整体思路和尾插是一样的,唯一的区别就在于多定义了一个tail指针变量,设置该指针变量唯一目的就是,为了是新的尾节点的next置成NULL,防止成为野指针。
// 单链表的头插
void SListPushFront(SListNode** pplist, SListDataType x)
{SListNode* NewNode = BuySListNode(x);NewNode->next = *pplist;*pplist = NewNode;
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{if (*pplist == NULL)//分情况,如果这种情况没有,下面cur->next就会导致对NULL访问的错误{return;//空就是没有要删的,直接返回}else{SListNode* cur = *pplist;*pplist = (*pplist)->next;free(cur);cur = NULL;//别忘记置成NULL,防止成为野指针}
}
头插相对比较简单,把我们新的节点中存放原先第一个节点的地址,然后把头指针的地址链接到我们所建立的新的节点上,时刻应该注意到链表的数据别丢失。
头删,建立一个临时的指针变量cur是一个关键,如果不建立,第一个节点已释放,找不到了之后的数据,把头指针指向第二个节点,那第一个节点的空间又无法释放,因此建立一个临时指针变量去存储其中一个地址,两个方法都可以。
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode** pos, SListDataType x)
{if (*pos == NULL){SListPushBack(pos, x);//这就是该接口也使用二级指针的原因,如果不使用,把&pos传过去} //最终改变不了test.c中的pList,只能改变pos的地址,因为一级指针pos只能改变pList所指向的空间内容else //1.要改变量内容传变量地址 { //2.要改变变量地址需要传变量地址的地址SListNode* next = BuySListNode(x);next->next = (*pos)->next;(*pos)->next = next;}
}
任意位置之后插入的原理其实和头删是一样的,就是要注意临时指针变量的建立,防止内存泄漏和野指针的问题,需要把插入位置的两头连上。唯一需要注意的是,如果没有*pos==NULL的情况,就不需要传二级指针,其实*pos==NULL就是尾插,可以直接调用尾插接口就可以了。
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{if (pos == NULL){return;}else if (pos->next == NULL){printf("你所要删除的位置之后没有数据\n");return;}else{SListNode* next = pos->next;pos->next = next->next;free(next);}
}
pos位置之后的删除也是同样的道理, 需要注意,就是说我们只能删除指定位置的后一个数据,插入也一样,因为我们都不知道这个位置的前一个节点的地址,这不是双链表,因此只能删除和插入pos之后的节点。
4.顺序表与链表区别
顺序表:可动态增长的数组,数据在数组中存储时必须是连续的。优点:可以随机访问,缓存命中率比较高。缺点:中间或者头部的插入删除很慢,需要挪动数据,时间复杂度是O(N)。空间不够时,增容会有一定消耗和空间浪费。
链表:逻辑上连续,物理上不一定连续。优点:头部插入只需修改指针指向即可,不需要挪动数据。缺点:缓存命中率比较低,不支持随机访问。
补充知识:
顺序表的缓存命中率比较高,为什么呢?因为顺序表的数据在物理上是连续的,因此cpu读取数据会从缓冲器上拿,第一次拿的时候,缓冲器没有我们在内存中存储的数据,因此缓冲器会去内存里拿,拿的时候会连续拿好几个数据,当cpu读取数据的时候就会先在缓冲器上拿,这样就会拿到数据,这就是缓冲命中率。
而链表物理上不是连续的因此缓冲器拿的时候拿不到连续的我们自己的数据,因此导致cpu每次在缓冲器上都拿不到数据,都会是缓冲器去内存上加载,再cpu在缓冲器上拿,而且也会导致缓冲器的污染,因为链表可能这一个那一个,缓冲器加载时候会把旁边不是它的数据也会捎带拿着,就会导致不是我们想要的数据也被加载到缓冲器。
相关文章:
【数据结构】顺序表和链表
1.线性表 我们在C语言当中学过数组,其实呢,数组可以实现线性表,线性表理解上类似于数组,那么什么是线性表呢?线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使 用的数据结构,常见…...
Training language models to follow instructions with human feedback解读
前置知识方法数据集结论 前置知识 GPT的全称是Generative Pre-Trained Transformer,预训练模型自诞生之始,一个备受诟病的问题就是预训练模型的偏见性。因为预训练模型都是通过海量数据在超大参数量级的模型上训练出来的,对比完全由人工规则…...
线性回归矩阵求解和梯度求解
正规方程求解线性回归 首先正规方程如下: Θ ( X T X ) − 1 X T y \begin{equation} \Theta (X^T X)^{-1} X^T y \end{equation} Θ(XTX)−1XTy 接下来通过线性代数的角度理解这个问题。 二维空间 在二维空间上,有两个向量 a a a和 b b b&…...
M3U8不知道如何转MP4?包能学会的4种格式转换教学!
在流媒体视频大量生产的今天,M3U8作为一种基于HTTP Live Streaming(HLS)协议的播放列表格式,广泛应用于网络视频直播和点播中。它包含了媒体播放列表的信息,指向了视频文件被分割成的多个TS(Transport Stre…...
C++第4课——swap、switch-case-for循环(含视频讲解)
文章目录 1、课程代码2、课程视频 1、课程代码 #include<iostream> using namespace std; int main(){/* //第一个任务:学会swap int a,b,c;//从小到大排序输出 升序 cin>>a>>b>>c;//5 4 3if(a>b)swap(a,b);//4 5 3 swap()函数是用于交…...
大数据新视界 -- 大数据大厂之大数据重塑影视娱乐产业的未来(4 - 4)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
在Java中,需要每120分钟刷新一次的`assetoken`,并且你想使用Redis作为缓存来存储和管理这个令牌
学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……) 2、学会Oracle数据库入门到入土用法(创作中……) 3、手把手教你开发炫酷的vbs脚本制作(完善中……) 4、牛逼哄哄的 IDEA编程利器技巧(编写中……) 5、面经吐血整理的 面试技…...
linux网络编程7——协程设计原理与汇编实现
文章目录 协程设计原理与汇编实现1. 协程概念2. 协程的实现2.1 setjmp2.2 ucontext2.3 汇编实现2.4 优缺点2.5 实现协程原语2.5.1 create()2.5.2 yield()2.5.3 resume()2.5.4 exit()2.5.5 switch()2.5.6 sleep() 2.6 协程调度器 3. 利用hook使用协程版本的库函数学习参考 协程设…...
Ubuntu22.04版本左右,扩充用户可使用内存
1 取得root权限后,输入命令 lsblk 查看所有磁盘和分区,找到想要替换用户可使用文件夹内存的磁盘和分区。若没有进行分区,并转为所需要的分区数据类型,先进行分区与格式化,过程自行查阅。 扩充替换过程,例如…...
基于ArcMap中Python 批量处理栅格数据(以按掩膜提取为例)
注:图片来源于公众号,公众号也是我自己的。 ArcMap中的python编辑器是很多本科生使用ArcMap时容易忽略的一个工具,本人最近正在读一本书《ArcGIS Python 编程基础与应用》,在此和大家分享、交流一些相关的知识。 这篇文章主要分享…...
【flink】之集成mybatis对mysql进行读写
背景: 在现代大数据应用中,数据的高效处理和存储是核心需求之一。Flink作为一款强大的流处理框架,能够处理大规模的实时数据流,提供丰富的数据处理功能,如窗口操作、连接操作、聚合操作等。而MyBatis则是一款优秀的持…...
Java设计模式—观察者模式详解
引言 模式角色 UML图 示例代码 应用场景 优点 缺点 结论 引言 观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知…...
【Cri-Dockerd】安装cri-dockerd
cri-dockerd的作用: 在k8s1.24之前。k8s会通过dockershim来调用docker进行容器运行时containerd,并且会自动安装dockershim,但是从1.24版本之前k8s为了降低容器运行时的调用的复杂度和效率,直接调用containerd了,并且…...
GCC及GDB的使用
参考视频及博客 https://www.bilibili.com/video/BV1EK411g7Li/?spm_id_from333.999.0.0&vd_sourceb3723521e243814388688d813c9d475f https://www.bilibili.com/video/BV1ei4y1V758/?buvidXU932919AEC08339E30CE57D39A2BABF6A44F&from_spmidsearch.search-result.0…...
大数据新视界 -- 大数据大厂之大数据重塑影视娱乐产业的未来(4 - 3)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
数据结构——基础知识补充
1.队列 1.普通队列 queue.Queue 是 Python 标准库 queue 模块中的一个类,适用于多线程环境。它实现了线程安全的 FIFO(先进先出)队列。 2.双端队列 双端队列(Deque,Double-Ended Queue)是一种具有队列和…...
只有.git文件夹时如何恢复项目
有时候误删文件但由于.git是隐藏文件夹而幸存,或者项目太大,单单甩给你一个.git文件夹让你自己恢复整个项目,该怎么办呢? 不用担心,只要进行以下步骤,即可把原项目重新搭建起来: 创建一个文件…...
anchor、anchor box、bounding box之间关系
最近学YOLO接触到这些概念,一下子有点蒙,简单总结一下。 anchor和anchor box Anchor:表示一组预定义的尺寸比例,用来代表常见物体的宽高比。可以把它看成是一个模板或规格,定义了物体框的“形状”和“比例”ÿ…...
代码随想录算法训练营第三十天 | 452.用最少数量的箭引爆气球 435.无重叠区间 763.划分字母区间
LeetCode 452.用最少数量的箭引爆气球: 文章链接 题目链接:452.用最少数量的箭引爆气球 思路: 气球的区间有重叠部分,只要弓箭从重叠部分射出来,那么就能减少所使用的弓箭数 **局部最优:**只要有重叠部分…...
海亮科技亮相第84届中国教装展 尽显生于校园 长于校园教育基因
10月25日,第84届中国教育装备展示会(以下简称“教装展”)在昆明滇池国际会展中心开幕。作为国内教育装备领域规模最大、影响最广的专业展会,本届教装展以“数字赋能教育,创新引领未来”为主题,为教育领域新…...
C语言数据结构学习:栈
C语言 数据结构学习 汇总入口: C语言数据结构学习:[汇总] 1. 栈 栈,实际上是一种特殊的线性表。这里使用的是链表栈,链表栈的博客:C语言数据结构学习:单链表 2. 栈的特点 只能在一端进行存取操作&#x…...
如何快速分析音频中的各种频率成分
从视频中提取音频 from moviepy.editor import VideoFileClip# Load the video file and extract audio video_path "/mnt/data/WeChat_20241026235630.mp4" video_clip VideoFileClip(video_path)# Extract audio and save as a temporary file for further anal…...
MongoDB 6.0 主从复制配置
以下是 MongoDB 6.0 版本配置主从的详细安装步骤: 1. 安装 MongoDB:可以从官网下载 MongoDB 6.0 的安装包并进行安装,或者使用相应的包管理工具进行安装。 2. 配置主节点:在主节点的 MongoDB 配置文件(默认路径为 …...
NPU 神经网络处理单元
Ⅰ 什么是 NPU? 当前正处于神经网络和机器学习处理需求爆发的初期。传统的 CPU(中央处理器)/GPU(图形处理器)可以执行类似任务,但专门为神经网络优化的 NPU(神经处理单元)比 CPU/GP…...
安宝特分享 | AR技术引领:跨国工业远程协作创新模式
在当今高度互联的工业环境中,跨国合作与沟通变得日益重要。然而,语言障碍常常成为高效协作的绊脚石。安宝特AR眼镜凭借其强大的多语言自动翻译和播报功能,正在改变这一局面,让远程协作变得更加顺畅。 01 多语言翻译优势 安宝特A…...
Vulkan 开发(五):Vulkan 逻辑设备
图片来自《Vulkan 应用开发指南》 Vulkan 开发系列文章: 1. 开篇,Vulkan 概述 2. Vulkan 实例 3. Vulkan 物理设备 4. Vulkan 设备队列 在 Vulkan 中,逻辑设备(Logical Device)是与物理设备(Physical D…...
Kafka 解决消息丢失、乱序与重复消费
一、引言 在分布式系统中,Apache Kafka 作为一种高吞吐量的分布式发布订阅消息系统,被广泛应用于日志收集、流式处理、消息队列等场景。然而,在实际使用过程中,可能会遇到消息丢失、乱序、重复消费等问题,这些问题可能…...
计算机专业毕业生面试工具推荐:白瓜面试
随着毕业季的临近,计算机专业的毕业生们即将步入职场,面试成为了他们必须面对的挑战。在这个过程中,选择合适的面试工具可以大大提高求职成功率。今天,我要向大家推荐一款专为计算机专业毕业生设计的面试工具——白瓜面试。 为什…...
数字IC开发:布局布线
数字IC开发:布局布线 前端经过DFT,综合后输出网表文件给后端,由后端通过布局布线,将网表转换为GDSII文件;网表文件只包含单元器件及其连接等信息,GDS文件则包含其物理位置,具体的走线࿱…...
高空作业未系安全带监测系统 安全带穿戴识别预警系统
在各类高空作业场景中,安全带是保障作业人员生命安全的关键防线。然而,由于人为疏忽或其他原因,作业人员未正确系挂安全带的情况时有发生,这给高空作业带来了巨大的安全隐患。为有效解决这一问题,高空作业未系安全带监…...
健康私人定制网站怎么做/怎样开网站
作者(Alex Rodriguez, Alessandro Laio)提出了一种很简洁优美的聚类算法, 可以识别各种形状的类簇, 并且其超参数很容易确定. 算法思想 该算法的假设是类簇的中心由一些局部密度比较低的点围绕, 并且这些点距离其他有高局部密度的点的距离都比较大. 首先定义两个值: 局部密度以…...
上海单位建设报建网站/网站文章优化技巧
在家装设计当中,卫生间的设计非常重要,因为它和我们联系十分紧密,而且使用也极其频繁,如果设计不过关的话,会极大降低我们的日常体验,在设计卫生间的时候,卫浴产品选择非常关键,因为…...
网站建设难做吗/百度推广的渠道有哪些
一.概念 列表视图;用来显示多个可滑动项列表的ViewGroup;需要适配器Adapter 将集合中数据和每一个Item所对应的布局动态适配到ListView中进行显示。 1 <?xml version"1.0" encoding"utf-8"?>2 <LinearLayout xmlns:andro…...
我做网站了 圆通/关键字挖掘爱站网
日萌社 人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新) 1.nn.BatchNorm1d(num_features)1.对小批量(mini-batch)的2d或3d输入进行批标准化(Batch Normalization)操作2.num_features:来自期望输…...
wordpress漏洞利用2016/竞价排名是按照什么来计费的
上传图片 API: wx.chooseImage() 和 wx.uploadFile() wx.chooseImage({count: 1, // 默认9sizeType: [original, compressed], // 可以指定是原图还是压缩图,默认二者都有sourceType: [album, camera], // 可以指定来源是相册还是相机,默认二者都有succe…...
百度网站优化/武汉百度
我是计科3的陈睿豪,一个土生土长的北京人,平时最喜欢的运动就是踢足球,在球场上我大多时间司职中场。我觉得自己比较善于和他人交朋友希望在大学可以交到更多的朋友。我对于计算机这个专业有很大的兴趣,但是我对于计算机的了解并不…...