当前位置: 首页 > news >正文

【数据结构】顺序表和链表

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语言当中学过数组&#xff0c;其实呢&#xff0c;数组可以实现线性表&#xff0c;线性表理解上类似于数组&#xff0c;那么什么是线性表呢&#xff1f;线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使 用的数据结构&#xff0c;常见…...

Training language models to follow instructions with human feedback解读

前置知识方法数据集结论 前置知识 GPT的全称是Generative Pre-Trained Transformer&#xff0c;预训练模型自诞生之始&#xff0c;一个备受诟病的问题就是预训练模型的偏见性。因为预训练模型都是通过海量数据在超大参数量级的模型上训练出来的&#xff0c;对比完全由人工规则…...

线性回归矩阵求解和梯度求解

正规方程求解线性回归 首先正规方程如下&#xff1a; Θ ( X T X ) − 1 X T y \begin{equation} \Theta (X^T X)^{-1} X^T y \end{equation} Θ(XTX)−1XTy​​ 接下来通过线性代数的角度理解这个问题。 二维空间 在二维空间上&#xff0c;有两个向量 a a a和 b b b&…...

M3U8不知道如何转MP4?包能学会的4种格式转换教学!

在流媒体视频大量生产的今天&#xff0c;M3U8作为一种基于HTTP Live Streaming&#xff08;HLS&#xff09;协议的播放列表格式&#xff0c;广泛应用于网络视频直播和点播中。它包含了媒体播放列表的信息&#xff0c;指向了视频文件被分割成的多个TS&#xff08;Transport Stre…...

C++第4课——swap、switch-case-for循环(含视频讲解)

文章目录 1、课程代码2、课程视频 1、课程代码 #include<iostream> using namespace std; int main(){/* //第一个任务&#xff1a;学会swap int a,b,c;//从小到大排序输出 升序 cin>>a>>b>>c;//5 4 3if(a>b)swap(a,b);//4 5 3 swap()函数是用于交…...

大数据新视界 -- 大数据大厂之大数据重塑影视娱乐产业的未来(4 - 4)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

在Java中,需要每120分钟刷新一次的`assetoken`,并且你想使用Redis作为缓存来存储和管理这个令牌

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 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权限后&#xff0c;输入命令 lsblk 查看所有磁盘和分区&#xff0c;找到想要替换用户可使用文件夹内存的磁盘和分区。若没有进行分区&#xff0c;并转为所需要的分区数据类型&#xff0c;先进行分区与格式化&#xff0c;过程自行查阅。 扩充替换过程&#xff0c;例如…...

基于ArcMap中Python 批量处理栅格数据(以按掩膜提取为例)

注&#xff1a;图片来源于公众号&#xff0c;公众号也是我自己的。 ArcMap中的python编辑器是很多本科生使用ArcMap时容易忽略的一个工具&#xff0c;本人最近正在读一本书《ArcGIS Python 编程基础与应用》&#xff0c;在此和大家分享、交流一些相关的知识。 这篇文章主要分享…...

【flink】之集成mybatis对mysql进行读写

背景&#xff1a; 在现代大数据应用中&#xff0c;数据的高效处理和存储是核心需求之一。Flink作为一款强大的流处理框架&#xff0c;能够处理大规模的实时数据流&#xff0c;提供丰富的数据处理功能&#xff0c;如窗口操作、连接操作、聚合操作等。而MyBatis则是一款优秀的持…...

Java设计模式—观察者模式详解

引言 模式角色 UML图 示例代码 应用场景 优点 缺点 结论 引言 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为设计模式&#xff0c;它定义了对象之间的一对多依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都会得到通知…...

【Cri-Dockerd】安装cri-dockerd

cri-dockerd的作用&#xff1a; 在k8s1.24之前。k8s会通过dockershim来调用docker进行容器运行时containerd&#xff0c;并且会自动安装dockershim&#xff0c;但是从1.24版本之前k8s为了降低容器运行时的调用的复杂度和效率&#xff0c;直接调用containerd了&#xff0c;并且…...

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)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

数据结构——基础知识补充

1.队列 1.普通队列 queue.Queue 是 Python 标准库 queue 模块中的一个类&#xff0c;适用于多线程环境。它实现了线程安全的 FIFO&#xff08;先进先出&#xff09;队列。 2.双端队列 双端队列&#xff08;Deque&#xff0c;Double-Ended Queue&#xff09;是一种具有队列和…...

只有.git文件夹时如何恢复项目

有时候误删文件但由于.git是隐藏文件夹而幸存&#xff0c;或者项目太大&#xff0c;单单甩给你一个.git文件夹让你自己恢复整个项目&#xff0c;该怎么办呢&#xff1f; 不用担心&#xff0c;只要进行以下步骤&#xff0c;即可把原项目重新搭建起来&#xff1a; 创建一个文件…...

anchor、anchor box、bounding box之间关系

最近学YOLO接触到这些概念&#xff0c;一下子有点蒙&#xff0c;简单总结一下。 anchor和anchor box Anchor&#xff1a;表示一组预定义的尺寸比例&#xff0c;用来代表常见物体的宽高比。可以把它看成是一个模板或规格&#xff0c;定义了物体框的“形状”和“比例”&#xff…...

代码随想录算法训练营第三十天 | 452.用最少数量的箭引爆气球 435.无重叠区间 763.划分字母区间

LeetCode 452.用最少数量的箭引爆气球&#xff1a; 文章链接 题目链接&#xff1a;452.用最少数量的箭引爆气球 思路&#xff1a; 气球的区间有重叠部分&#xff0c;只要弓箭从重叠部分射出来&#xff0c;那么就能减少所使用的弓箭数 **局部最优&#xff1a;**只要有重叠部分…...

海亮科技亮相第84届中国教装展 尽显生于校园 长于校园教育基因

10月25日&#xff0c;第84届中国教育装备展示会&#xff08;以下简称“教装展”&#xff09;在昆明滇池国际会展中心开幕。作为国内教育装备领域规模最大、影响最广的专业展会&#xff0c;本届教装展以“数字赋能教育&#xff0c;创新引领未来”为主题&#xff0c;为教育领域新…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

接口自动化测试:HttpRunner基础

相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具&#xff0c;支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议&#xff0c;涵盖接口测试、性能测试、数字体验监测等测试类型…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...