数据结构和算法:(3)3.2线性表的链式存储结构
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素也就是说你这个可以放在A地点,这个可以放在E地点,A地点和E地点中间可以隔开一个C地点和D地点,这样是允许的),这组存储单元可以存在内存中未被占用的任意位置。
比起顺序存储结构每个顺序存储结构只需要存储一个位置就可以了。现在链式存储结构中,除了要存储数据元素信息外,还要存储他的后继元素的存储地址(指针)。也就是说除了存储其本身的信息外,还需存储一个指示其直接后继的存储位置的信息。
我们把存储数据元素信息的域称为数据域(域就是地方的意思),把存储直接后继位置的域称为指针域。指针域中存储的信息称为指针或链,这两部分信息组成数据元素称为存储映像,称为结点(Node)。
n个结点链接成一个链表,即为线性表(a1,a2,a3,...,an)的链式存储结构。
因为此链表的每个结点中只包含一个指针域,所以叫做单链表。(后边还有其它链表)如下图:
对于线性表来说,总得有个头有个尾(这样你才能知道从哪里开始从那里结束),链表也不例外,我们把链表中的第一个结点的存储位置叫做头指针,最后一个结点指针为空(NULL)表示结束。
头指针与头结点的异同:(头结点的数据域一般不存储任何信息。(第一个的特权))
--头指针:
- 头指针是指链表指向第一个结点的指针,若链表有头结点的话,那么头指针是在头结点之前指向头结点的指针。
- 头指针具有标识作用,所以常用头指针冠以链表的名字(指针变量的名字)。(意思就是使用指针变量的名字的我们叫做这个链表的名字)
- 无论链表是否为空,头指针均不为空。(因为头指针为空就没有这个链表了)(空链表也是一个链表)
- 头指针是链表的必要元素。
--头结点:
- 头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(但也可以用来存放链表的长度)(就是说他的数据域可以写一个变量,一个变量存放链表的长度,那么通过索引他就可以知道链表的当前长度)。
- 有了头结点,对在第一个元素结点前插入结点和删除第一个结点的操作与其它结点的操作就统一了。
- 头结点不一定是链表的必要元素。
(如下图:单链表图例)
头指针指向头结点,头结点的数据域一般是空的(一般是没有任何东西的,但你也可以存放当前这个链表的长度,(因为数据结构都是可以自己定义的)),然后头结点指向我们的第一结点(就是有元素的a1的位置),接着往下。
(空链表的图如下:)
空链表是头指针、头结点、NULL。(如果没有头结点的话就是头指针指向NULL)
我们在C语言中可以用结构指针来描述单链表。如下:
//1我们这里定义一个结点
typedef struct Node//2typedef是取别名
{ElemType data;//4数据域struct Node* Next;//5指针域(他是指向结点类型的指针,因为他下一个元素就是结点,这个结点类型就是ode,加上一个*号表示他是一个指针)
}Node;
typedef struct Node* LinkList;// 3Node*表明他是一个指针,取别名为LinkList(6有点像define,也就是说这是类型的别名。只要在文章中出现LinkList他就相当于Node*,指向一个Node 的指针)
//我们看到结点由存放数据元素的数据域和存放后继结点地址的指针域组成。
假设P是指向线性表第i个元素的指针,则该结点ai的数据域我们可以用P->data的值是一个数据元素,结点ai的指针域(可以指向下一个结点的地址)可以用P->next(next是指向下一个结点的指针)来表示。
P->next的值是一个指针。
那么P->next指向的是谁呢?当然指向第i+1个元素!也就是指向ai+1的指针。即P->data=ai,则P->next->data=ai+1(P->next下个结点的地址也是一个结点结构,那么结点结构就有一个data,所以这个就是取下一个结点的数据他的元素的值)。
---1、单链表的读取
在单链表中我们要找的第i个元素到底在哪,我们必须从第一个结点开始挨个找。
因此对于单链表实现获取第i个元素的数据操作GetElem的算法思路是:
- 声明一个结点p指向链表第一个结点,初始化j从1开始;
- 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j+1;
- 若到链表末尾p为空,则说明第i个元素不存在;
- 否则查找成功,返回结点p的数据。
GetElem.c代码如下:
//初始条件:顺序线性表L已经存在,1<=i<=Listlength(L)
//操作结果:用e返回L中第i个数据元素的值。
Status GetElem(LinkList L,int i,ElemType *e)
{int j;LinkList p;//由前可知,LinkList就是链表(Node*取了别名)Node*指向了一个p,p这里就是一个指针(指向一个结点的指针)(声明一个指针p)p=L->next;//让p指向L链表的第一个结点j=1;//我们把当前计数器设置为1,初始化while(p&&j<i)//若p且j小于i(要符合两个条件)表示未找到{p=p->next;//未找到指向下一个++j;}if(!p||j>i)//左边为假,说明这时候已经到了结束的地方了,链表已经整个索引结束还没中找到他,p应该是等于NULL为空,那么!p就为真,我们并没有找到链表已经结束了,所以他会返回一个错误。第二个情况j>i{return ERROR;}*e=p->data;//排除以上两种情况就是找到了,就可以把他的元素给了e变量。return OK;//表示查找成功
}
//就是从头开始找,直到第i个元素为止。
//因为上述单链表的结构中没有定义表长,所以不能实现知道要循环多少次,因此也就不能方便使用for来控制循环。(最主要的就是工作指针后移)
2、单链表的插入
假设存储元素e的结点为s,要实现结点p、p->next和s之间逻辑关系的变化,如下图:
由上图我们可以看到,在元素ai这个存放的地址处,我们说了单链表需要用两个单元的空间来存放一个结点,前边的空间呢是这个存放的元素,第二个空间呢是存放下一个结点的位置的地址,那假设ai为结点p那么p->next就是ai后边的地址(ai后边空间存放的)p有两个产权,一个是ai一个是next(地址指向就是下一个),s就是计划要插入的结点,也就是元素e所在的结点,那么如何将结点s插入到ai和ai+1之间呢?
我们思考后发现压根就不用惊动其他的结点,只需要让s->nest和p->next的指针做一点改变。(我们只需要p->next指向s结点,让s->next指向我们原来p->next指向的ai+1的结点就可以了。)
- (1)s->next=p->next;
- (2)p->next=s
PS:切记不能将上述两个语句调换顺序,因为如果先执行p->next的话会被覆盖为s的地址,那么再执行s->next=p->next其实就等于s->next=s了。
单链表第i个数据插入结点的算法思路:
- 声明一结点p指向指向链表头结点,初始化j从1开始;
- 当j<1时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1;
- 若到链表末尾p为空,则说明第i个元素不存在;
- 否则查找成功,在系统中生成一个空结点s;(我们需要在一个链表里面插入一个元素那我们应该生成一个空的结点,然后把元素放在这个结点的第一个位置,然后将第二个位置指向下一个元素;让他的上一个元素指向他的这个位置,就可以了。)
- 将数据元素e赋值给s->data;
- 单链表的插入刚才两个标准语句;
- 返回成功。
代码如下:
//初始条件:顺序线性表L已经存在,1<=i<=Listlength(L)
//操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1。
Status GetElem(LinkList *L,int i,ElemType *e)
{int j;LinkList p,s;//由前可知,LinkList就是链表(Node*取了别名)Node*指向了一个p,p这里就是一个指针(指向一个结点的指针)(声明一个指针p)p=*L;j=1;//我们把当前计数器设置为1,初始化
//while循环是为了寻找第i个结点。 (j从1开始便利直到i的时候,如果找到了她就等于i嘛,循环就会退出。)while(p&&j<i)//若p且j小于i(要符合两个条件)表示未找到(&&其中一个为假的时候循环就会退出,要么他永远都找不到,要么p为NULL已经指向了结尾。那么也就是退出,在下边再判断){p=p->next;//未找到指向下一个++j;}if(!p||j>i)//左边为假,说明这时候已经到了结束的地方了,链表已经整个索引结束还没中找到他,p应该是等于NULL为空,那么!p就为真,我们并没有找到链表已经结束了,所以他会返回一个错误。第二个情况j>i,已经找不到了。{return ERROR;}s=(LinkList)malloc(sizeof(Node));//那么我们找到这个位置之后就用malloc生成一个新的结点,我们这个结点时Node一个结构,不再写8个字节还是16个字节用sizeof提高了代码的灵活性。(在编写代码要经常考虑灵活性和普遍性问题),(LinkList)他的意思是再把他强制转化为LinkList的形式,这是一个确保。s->data=e;//赋值s->next=p->next;p->next=sreturn OK;//表示查找成功
}
//就是从头开始找,直到第i个元素为止。
3、单链表的删除操作
由上图我们可以看出,插入前与插入后的变化。删除的话我们假设a1是结点p,a2叫做结点q,我们想把结点q给他去掉,那么a1就指向了a3,a2在哪里?不用管他。我们没有必要说把a2给清零,直接把a2的指针给去掉就可以了(a1就直接忽视a2就直接指向a3,我们不用关心a2会不会飘到哪里,在那里驻扎;不用管他啦,因为在内存中都会有一些随随机的数据,只要他的地址没有用到,他的这个数据就是随机的,就是没用的,就是随即可能被覆盖的。所以不用他)。
- 假设元素a2的结点为q,要实现结点q删除单链表的操作,其实就是将他的前继结点的指针绕过指向后继结点即可。
- 那我们所要做的,实际上就是一布操作:
- 可以这样:p->next=p->next->next;
- 或者也可以这样:q=p->next; p->next=q->next;
单链表第i个数据删除结点的算法思路:
- 声明结点p指向链表第一个结点,初始化j=1;
- 当j<1时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
- 若到链表末尾p为空,则说明第i个元素不存在;
- 否则查找成功,将欲删除结点p->next赋值给q;
- 单链表的删除标准语句p->next=q->next;
- 将结点中的数据赋值给e,作为返回;
- 释放q结点。
代码如下:
//初始条件:顺序线性表L已经存在,1<=i<=Listlength(L)
//操作结果:删除L的第i个数据元素,并用e返回其值,L的长度-1。
Status GetElem(LinkList *L,int i,ElemType *e)
{int j;LinkList p,q;//由前可知,LinkList就是链表(Node*取了别名)Node*指向了一个p,p这里就是一个指针(指向一个结点的指针)(声明一个指针p)p=*L;j=1;//我们把当前计数器设置为1,初始化
//while循环是为了寻找第i个结点。 (j从1开始便利直到i的时候,如果找到了她就等于i嘛,循环就会退出。)while(p->next&&j<i)//若p且j小于i(要符合两个条件)表示未找到(&&其中一个为假的时候循环就会退出,要么他永远都找不到,要么p为NULL已经指向了结尾。那么也就是退出,在下边再判断){p=p->next;//未找到指向下一个++j;}if(!p->next||j>i)//左边为假,说明这时候已经到了结束的地方了,链表已经整个索引结束还没中找到他,p应该是等于NULL为空,那么!p就为真,我们并没有找到链表已经结束了,所以他会返回一个错误。第二个情况j>i,已经找不到了。{return ERROR;}q=p->next;p->next=q->next;*e=q->data;free(q);//或者delete q;return OK;//表示查找成功
}
//就是从头开始找,直到第i个元素为止。
效率对比:我们希望从第i个位置开始,插入连续10个元素,对于顺序存储结构意味着每一次插入都需要移动n-i个位置,所以每次都是O(n)。而单链表我们只需要在第一次时,找到第i个位置的指针,此时为O(n),接下来只是简单的通过赋值移动指针而已,时间复杂度都是O(1)。所以,对于插入和删除越频繁的操作,单链表的效率优势就越明显。(这样我们在存储一大堆数据的时候就不会只是想到数组,可以考虑一下单链表或双链表)
数据结构和算法:(3)3.2线性表的链式存储结构相关推荐
- 从零开始学数据结构和算法(二)线性表的链式存储结构
链表 链式存储结构 定义 线性表的链式存储结构的特点是用一组任意的存储单元的存储线性表的数据元素,这组存储单元是可以连续的,也可以是不连续的. 种类 结构图 单链表 应用:MessageQueue 插 ...
- 《数据结构》c语言版学习笔记——单链表结构(线性表的链式存储结构Part1)
线性表的链式存储结构 数据结构系列文章 第二章 单链表结构 文章目录 线性表的链式存储结构 前言 一.单链表的建立 代码 二.单链表的读取 代码 三.单链表的插入 代码 四.单链表的删除 代码 五.单 ...
- 【数据结构】CH2 线性表的链式存储结构
目录 一.链表概述 1.相关定义 二.单链表 1.插入和删除节点的操作 (1)插入结点 (2)删除结点 2.建立单链表 (1)头插法 (2)尾插法 3.线性表基本运算在单链表中的实现 (1)初始化线性 ...
- 数据结构-线性表(链式存储结构)
线性表(链式存储结构) 特点: 用一组任意的存储单元存储线性表的数据结构,这组存储单元可以是连续的,也可以是不连续的. 对数据结构ai来说,除了存储其本身的信息之外,还需存储一个指示其后继的信息(即直 ...
- 《数据结构》c语言版学习笔记——其他链表(线性表的链式存储结构Part2)
线性表的链式存储结构 数据结构系列文章 第三章 循环链表.双向链表 文章目录 线性表的链式存储结构 前言 一.循环链表 (一)定义 (二)尾指针 二.双向链表 (一)定义 (二)代码 总结 前言 提示 ...
- Python 数据结构 之 线性表 的链式存储结构
用Python 来实现 C语言中 线性表的链式存储结构. 文章转载请注明: Python 数据结构 之 线性表 的链式存储结构 代码地址 https://github.com/WenkeZhou/P ...
- 数据结构开发(5):线性表的链式存储结构
0.目录 1.线性表的链式存储结构 2.单链表的具体实现 3.顺序表和单链表的对比分析 4.小结 1.线性表的链式存储结构 顺序存储结构线性表的最大问题是: 插入和删除需要移动大量的元素!如何解决? ...
- 链表list(链式存储结构实现)_5 线性表的链式存储结构
系列文章参考资料为<大话数据结构>,源码为个人私有,未经允许不得转载 线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,可以使连续的,也可以不连续,也就意味这些元素可以 ...
- 线性表(链式存储结构)
前言 线性表(顺序存储结构-用数组描述) 为了解决顺序存储不足:用线性表另外一种结构-链式存储.在顺序存储结构(数组描述)中,元素的地址是由数学公式决定的,而在链式储存结构中,元素的地址是随机分布的, ...
最新文章
- visualVM 安装使用
- qt 在移动的两点之间连线_几种移动端跨平台技术区别
- mysql从某表中查询数据插入到另一表的处理
- 数据分析基础教程Numpy指南笔记
- 如何避免重复请求/并发请求?这样处理才足够优雅
- acid(数据库事务正确执行的四个基本要素的缩写)
- Linux入门学习(五)
- 操作系统(13)-操作系统中的死锁及其预防、避免、检测与解除
- ABP框架 - 多租户
- Android Material Components – MaterialAlertDialog
- 老罗Android开发视频教程 (android解析xml文件 )3集集合
- 基本的广告法违禁词、违规词以及敏感词大全
- 微信小程序登录界面 服务器,微信小程序之登录页-------实例
- Pytorch-早停法(early stopping)原理及其代码
- 立项 ——VC下,打印机管理和维护
- 论文精读 清华ERNIE:Enhanced Language Representation with Informative Entities
- 图像编辑系列之(2)基于StyleGAN(3)GAN逆映射(4)人脸 (5)语义生成 | ICCV2021生成对抗GAN梳理汇总...
- 【PaddlePaddle】【论文复现】U-GAT-IT
- 如何将中文地址翻译成英文
- 网易伏羲私有云在资源调度及资源整合方面的实践
热门文章
- 2021年大数据Hive(七):Hive的开窗函数
- php xdebug 中文手册,php 安装xdebug扩展
- Android drawLine 画虚线
- Iframe上传文件
- mariadb(第二章)增删改 MariaDB 数据类型
- Can't create table... error150
- Go 学习笔记(16)— 函数(02)[函数签名、有名函数、匿名函数、调用匿名函数、匿名函数赋值给变量、匿名函数做回调函数]
- 28自定义View 模仿联系人字母侧栏
- php 二维数组排序,多维数组排序
- Android窗口管理服务WindowManagerService计算窗口Z轴位置的过程分析