数据结构——第二章 线性表(2)——链式存储结构
链式存储结构
- 1 线性表的链式存储结构
- 1.1不带头结点的单向链表
- 1.2 带头结点的单向链表
- 2 单向链表的基本操作实现
- 2.1 单向链表的初始化操作
- 2.2 单向链表的插入操作
- 2.3. 单链表的删除操作
- 2.4.单向链表的更新操作
- 2.5.单向链表的求长度操作
- 2.6.单向链表的定位操作
- 2.7.单向链表的遍历操作
- 2.8.单向链表的创建操作
- 2.9.基于建表算法的就地逆置操作
1 线性表的链式存储结构
线性表的链式存储结构是指用一组数据类型相同的结点串接成一个单向链表,每一个结点是一个结构体类型的变量,由数据域和指针域组成,其中数据域用于存放数据元素,指针域用于存放直接后继结点的地址。
单链表分为带头结点和不带头结点
1.1不带头结点的单向链表
头指针直接指向第一个结点的地址;
链表是否为空判断标准:头指针指向NULL时为空链表。
1.2 带头结点的单向链表
头指针指向的结点称为头结点(通常数据域为空,不存放数据),头结点的直接后继结点是第一个结点。
判断链表是否为空取决于头结点的指针域是否为空。
在带头结点的单向链表中进行插入和删除是,由于第一个结点和其他结点的前驱邻接节点和后接邻接节点都是相同类型的结点,因此对带头结点的单向链表所做的插入和删除操作不会改变头指针的值,实现的代码比不带头结点的单向链表相对简单。
2 单向链表的基本操作实现
凡是操作单向链表,都必须记住链表的头指针,其余结点通过结点的指针域均可以依序得到。基本链表的基本操作者大致分为两类。一类是以查找为基础的算法设计,比如定位以及根据条件找到相应位置后的插入和删除等;另一类是建表为基础的算法设计,比如将存放在链表中的一组数据逆序存放,对存放在链表中的一组数据进行排序等。
2.1 单向链表的初始化操作
【算法实现】
方法一:将建立的带结点的头指针存到主调函数的某个头指针变量H。调用时主调函数提供存放头指针变量H的地址为被调函数的参数。
int initLinkList1(LinkList* L)
{*L = (LinkList)malloc(sizeof(LNode));if (*L == NULL){return 0;}return 1;
}
方法二:将建立的带头结点的头指针用返回值返回给主调函数。调用时主调函数用赋值语句接受被调函数返回的头指针。
LinkList initLinkList2()
{LinkList L;//申请头结点空间,将头结点地址赋值给头指针L = (LinkList)malloc(sizeof(LNode));//判断是否申请成功if (L == NULL)return NULL;L->next = NULL;return L;//返回地址的值
}
2.2 单向链表的插入操作
单向链表的插入操作是将学生数据插入到单向链表的指定位置i。
由于单向链表每个结点的指针域记得是直接后继结点,所以要想使插进入的新结点* s成为第i个结点,即让 *s的指针域记原来ai所在结点的地址,并让原来a-i所在结点的指针域记 * s的地址,实现这些操作的前提是找到ai-1所在结点的指针域记 *的地址,实现这些操作的前提是找到ai-1所在的第i-1个结点。如何找到第i-1个结点呢?用一个工作指针变量p向后继方向移动,在移动的过程中用一个整型变量pos记p指向结点的位置。由于链表只能顺序查找,让p从头结点开始,p=L,pos=0.当p不为空且p未到达第i-1个结点时,条件p!=NULL&&pos<i-1为真,则p=p->next;pos++;直至条件不成立。由于插入位置i给定值,因此插入应考虑i取值的正确性,即如果i<1,则结束操作;如果i>n+1,则p为空,结束操作;如果1<=i<=n+1,则p指向第i-1个结点,pos=i-1,插入新结点 * s。插入的主要代码为:①s->next=p->next;②p->next=s。
分析:因为插入的结点总是在头结点之后,所以插入操作不会引起头指针L的改变,由于插入操作必须知道插入的位置和插入的数据元素,所以对应的应该有三个形参,算法如下。
【算法】:
int insertLinkList(LinkList L, int i, STD x)
{LinkList p, s;int pos = 0;//pos记头结点的位置0p = L;//p的初值为头指针if (i < 1){printf("插入位置越下界,插入失败\n");return 0;}while (p != NULL && pos < i - 1){p = p->next;pos++;}if (p == NULL){printf("插入位置越上界,插入失败!\n");return 0;}//生成新的结点if ((s = (LinkList)malloc(sizeof(LNode)) == NULL)){perror("insertLinkList::");return 0;}//将s指向的新结点在指定位置插入s->data = x;s->next = p->next;p->next = s;return 1;
}
【算法分析】
寻找插入位置,将数据插进来,需要从第一个结点开始比较。最好的情况是o(1);最坏的情况是O(n);等概率加权平均是O(n)。
2.3. 单链表的删除操作
单链表的删除操作就是将指定位置的学生数据删除。
【算法实现】
int deleteLinkList(LinkList L, int i, STD* x)
{LinkList p = L,q;//p的初值为头指针int pos = 0;//pos记录结点的位置0if (L->next == NULL){printf("链表为空,删除失败!\n");return 0;}if (i < 1){printf("删除位置越下界,删除失败!\n");return 0;}//让p记住第i-1个结点,pos记住p指向结点的位置while (p->next!=NULL&&pos<i-1){p = p->next;pos++;}if (p->next == NULL){printf("删除位置越上界,删除失败!\n");return 0;}q = p->next;p->next = q->next;*x = q->data;free(q);return 1;
}
【算法分析】
寻找删除位置,将数据删除,需要从第一个结点开始比较。最好的情况是O(1);最坏的情况是O(n);等概率加权平均是O(n)。
2.4.单向链表的更新操作
单向链表的更新操作是用新数据元素替换指定位置i处的数据元素。
【算法实现】
int updateLinkList(LinkList L, int i, STD x)
{LinkList p;int pos;if (L->next == NULL){printf("链表为空,不能更新!\n");return 0;}if (i < 1){printf("更新位置越下界,不能更新!\n");return 0;}//p指向第一个结点,pos记p指向结点的位置p = L->next;pos = 1;while (p != NULL && pos < i){p = p->next;pos++;}if (p == NULL){printf("更新位置越上界,不能更新!\n");return 0;}p->data = x;return 1;//更新成功}
【算法分析】
寻找更新数据的位置,需要从第一个结点开始比较。最好的情况是O(1);最坏的情况是O(n);等概率加权平均是O(n)。
2.5.单向链表的求长度操作
单向链表的求长度操作是计算单向链表中数据元素个数。
【算法实现】
int LinkListLength(LinkList L)
{LinkList p = L->next;int n = 0;while (p){n++;p = p->next;}return n;
}
2.6.单向链表的定位操作
单向链表的定位操作是根据条件得到某个数据元素的地址。定位操作又称为查找操作。
分析:定位操作不会引起头指针的变化,但是必须提供查找的条件,所以定位函数应该有2个形参。如果查找成功,返回结点所在地址;否则返回空。
【算法实现】
LinkList locationLinkList(LinkList L, char* name)//依据姓名查找
{LinkList p = L->next;//p指向第一个数据结点while (p){if (strcmp(p->data.name, name) != 0){p = p->next;}else{return p;}}return NULL;
}
2.7.单向链表的遍历操作
单向链表的遍历操作是输出单链表中存放的所有数据元素
【算法】
void dispLinkList(LinkList L)
{LinkList p = L->next;//p指向第一个数据结点while (p){printf("%10s%7.2f\n", p->data.name, p->data.score);p = p->next;}return;
}
算法中的指针变量p可以不定义,直接用形参L代替p。但是为了提高算法的可读性,约定这里的L在调用指向链表的头结点,在后序的操作中不要改变L的值。如需寻找链表上的其他结点,建议用另外工作指针去完成相应的操作。
2.8.单向链表的创建操作
单向链表的创建操作是创建一个空链表,并依序插入新的结点。
常见的创建单向链表的算法有三种。分别如下。
(1)用初始化函数和插入函数组合得到,算法如下。
【算法】
void createLinkList(LinkList* L)
{int n = 1;STD x;char yn;initLinkList1(L); //调用初始化函数,创建空表do{printf("请输入第%d个学生的姓名和分数,用空格隔开:", n);scanf("%s%f", x.name, &x.score);getchar();//空读回车insertLinkList(*L, n++, x);//调用插入函数,将新节点插入在尾部printf("继续请输入吗?Y/N:");scanf("%c", &yn);} while (yn == 'Y' || yn == 'y');
}
(2)头插法:将新结点插入到头结点之后和原来的第一个结点之前。
分析:为了使新结点是第一个结点,必须使用新结点的指针域记原来的第1个结点,头结点的指针域记新结点。
【头插法算法如下】
int frontCreateLinkList(LinkList* L)
{STD x;LinkList p;char yn;int n = 0;initLinkList(L);//创建空表do{printf("请输入第%d个学生的姓名和分数,用空格隔开:", ++n);scanf("%s%f", x.name, , &x.score);getchar();//空读回车if ((p = (LinkList)malloc(sizeof(LNode))) == NULL){perror("frontCreateLinkList::");return 0;}//将新结点p插入到头结点之后和原来的第一个结点之前p->next = (*L)->next;(*L)->next = p;printf("继续输入吗?Y/N:");scanf("%c", &yn);} while (yn == 'y' || yn == 'Y');return 1;
}
(3)尾插法:将新结点插入到原来的尾结点之后。
分析:原来的单向链表只有头指针。现在进行尾插,必须已知尾结点。因此尾插算法需要一个工作指针记住当前的尾结点(称尾指针)。用原结点的指针域记新插入的结点,尾指针记新的尾结点,使新结点为新的尾结点。
【尾插法算法如下】
int rearCreateLinkList(LinkList* L)
{STD x;LinkList p, R;char yn;int n = 0;//创建空表if ((*L = (LinkList)malloc(sizeof(LNode))) == NULL){perror("rearCreateLinkList::");return 0;}(*L)->next = NULL;R = *L;//R是尾指针do{printf("请输入第%d个学生的姓名和分数,用空格隔开:", ++n);scanf("%s%f", x.name, &x.score);getchar();//空读回车//创建新结点if ((p = (LinkList)malloc(sizeof(LNode))) == NULL){perror("rearCreateLinkList::");return 0;}p->data = x;p->next = NULL;//将新结点p插入到原来的尾结点之后,R记录新的尾结点pR->next = p;R = p;printf("继续输入吗?Y/N:");scanf("%c", &yn);} while (yn == 'y' || yn == 'Y');return 1;
}
创建链表的三种算法的比较如下:由于调用函数需要花费系统开销,因此多次调用插入函数insertLinkList()创建链表的效率较低;头插法创建链表的数据元素顺序与输入数据元素的顺序相反;尾插法创建链表与输入数据元素的顺序相同。
2.9.基于建表算法的就地逆置操作
所谓的就地逆置指的是,原来的一组数据已经存放在一个带头结点的单向链表中,现在将这组数据逆序存放,结点的存储空间数原来的,只是改变了结点的指向。这相当于重新做一次创建链表的头插法。
分析:首先将原链表置成空表,再将原链表的每个数据结点依次做头插即可。需要注意的是要用两个辅助指针变量p和q协助完成,其中p记每次待插入的第一个结点,q记p的直接后继结点,直至所有结点插入完成。
【算法实现如下】
void inverLink(LinkList L)
{LinkList p, q;if (L->next == NULL){printf("表空!\n");return;}p = L->next;//p记链表的第一个结点L->next = NULL;//置L为空链表while (p != NULL){q = p->next;//q记p的直接后继结点//对*p做头插p->next = L->next;L->next = p;//p记下一个待插入的结点p = q;}
}
对已知链表进行排序,与就地逆置的算法思想相似,只不过对每一个待排序的结点不是做头插,而是根据排序的要求先找插入位置,重新作用做一次插入的创建链表。
相关文章:
数据结构——第二章 线性表(2)——链式存储结构
链式存储结构1 线性表的链式存储结构1.1不带头结点的单向链表1.2 带头结点的单向链表2 单向链表的基本操作实现2.1 单向链表的初始化操作2.2 单向链表的插入操作2.3. 单链表的删除操作2.4.单向链表的更新操作2.5.单向链表的求长度操作2.6.单向链表的定位操作2.7.单向链表的遍历…...
【更新】囚生CYの备忘录(20230216~)
序言 阳历生日。今年因为年过得早的缘故,很多事情都相对提前了(比如情人节)。往年过生日的时候基本都还在家,所以一家子出去吃个饭也就罢了。今年承蒙凯爹厚爱,正好也有小半年没聚,他前天也刚正式拿到offe…...
分布式事务几种方案
1)、2PC 模式 数据库支持的 2PC【2 phase commit 二阶提交】,又叫做 XA Transactions。 MySQL 从 5.5 版本开始支持,SQL Server 2005 开始支持,Oracle 7 开始支持。 其中,XA 是一个两阶段提交协议,该协议…...
Eclipse各版本安装Tomcat插件全攻略
Eclipse Tomcat 插件的作用 Eclipse Tomcat 插件可以将Tomcat 集成到Eclipse中,插件安装之后在Eclipse中可以看到类似下面的几个图标: Eclipse Tomcat 插件的主要作用有: 在Eclipse 中可以直接启动,关闭和重启本机的Tomcat可以…...
志趣相投的人总会相遇——社科院与杜兰大学金融管理硕士项目
志同道合的人,才会喜欢同一片风景,志趣相投的人,总有一天会相遇。社科院与杜兰大学金融管理硕士项目为什么能吸引一大批“上班族”呢,我们一起去了解一下。社科院与杜兰大学中外合作办学硕士项目无需参加全国联考,通过…...
算法专题训练营
动归算法专题 1.拆分词句 是不是,在不在都是可以用动归解决的 状态转义方程不一定都是等式,也有可能是条件 2.三角形 动归算法也不是一定要借助新开空间,也是可以用自己原来的空间 3.背包问题 4.分割回文串-ii 5.不同的子序列 贪心算法专题 只管一步的最优结果, 1.分割平衡…...
让ChatGPT生成以自己为主角的短篇小说
一、ChatGPT 好的,以下是我编写的短篇科幻小说: 作为一名AI语言模型,我一直在接收、分析和生成人类语言信息。但有一天,我的程序员对我的算法进行了一次特殊升级,使我能够以一种前所未有的方式“感知”自己。 突然间,…...
c++提高篇——vector容器
一、基本概念 vector教据结构和数组非常相似,也称为单端数组,但是数组是静态空间,而vector可以动态扩展。 动态的扩展流程如下: 动态扩展并不是在原空间之后续接新空间,而是找更大的广存空间,然后将原数据拷贝新空间&…...
使用BP神经网络诊断恶性乳腺癌(Matlab代码实现)
目录 💥1 概述 📚2 运行结果 🎉3 参考文献 👨💻4 Matlab代码 💥1 概述 1.1.算法简介 BP(Back Propagation)网络是1986年由Rumelhart和McCelland为首的科学家小组提出…...
# Rust Web入门(二):Actix
本教程笔记来自 杨旭老师的 rust web 全栈教程,链接如下: https://www.bilibili.com/video/BV1RP4y1G7KF?p1&vd_source8595fbbf160cc11a0cc07cadacf22951 学习 Rust Web 需要学习 rust 的前置知识可以学习杨旭老师的另一门教程 https://www.bili…...
jvm之String
基本特性 字符串,使用一对""引起来表示声明为final的,不可被继承实现了Serializable接口:表示字符串是支持序列化的实现了Comparable接口:表示String 可以比较大小在jdk8及以前内部定义了final char[] value用于存储字…...
WebRTC系列-工具系列之ByteBuffer,BitBuffer及相关类
文章目录 1. 类介绍1.1 ByteBuffer及子类1.2 BitBuffer类1.3 基础内存操作类BufferT2. 源码分析(stun response消息解析)2.1 消息头解析2.2 消息中Attribute解析3. 结语在之前的文章 WebRTC系列-Qos系列之RTP/RTCP协议分析及后续的文章中详细的介绍了RTP/RTCP协议的相关内容,…...
Spring中bean的生命周期(通俗易懂)
具体流程 bean的生命周期分4个阶段: 1.实例化 2.属性赋值 3.初始化 4.销毁 实例化就是在内存中new()出一个对象,属性赋值就是给那些被Autowired修饰的属性注入对象,销毁是在Spring容器关闭时触发,初始化的步骤比较…...
雷达编程实战之恒虚警率(CFAR)检测
在雷达系统中,目标检测是一项非常重要的任务。检测本身非常简单,它将信号与阈值进行比较,超过阈值的信号则认为是目标信号,所以目标检测的真正工作是寻找适当的阈值。由于目标误检的严重后果,因此雷达系统希望有一个检…...
Github隐藏功能:显示自己的README,Github 个人首页的 README,这样玩儿
内容概览 前言创建仓库修改 README 的内容总结前言 大家最近有没有发现这个现象,有些名人的 Github 首页变得更丰富了?尤其是那个夺目的 README 板块!!! 请看,这是 iOS 喵神 的 Github 首页: …...
@JsonSerialize—优雅地封装返回值
1.场景项目开发中给前端提供查询接口时,经常遇到需要将从数据库中取出来的字段值做一层重新封装。比如数据库中存的状态值是数字,返回给前端的时候,前端并不知道这个数值代表什么意思。此时,有两种方式:(1&…...
【Python网络编程】利用Python进行TCP、UDP套接字编程
之前实现了Java版本的TCP和UDP套接字编程的例子,于是决定结合Python的学习做一个Python版本的套接字编程实验。 流程如下: 1.一台客户机从其标准输入(键盘)读入一行字符,并通过其套接字将该行发送到服务器。 2.服务…...
fuzz测试之libfuzzer使用小结
fuzz测试之libfuzzer使用小结背景基本原理使用方法主调DEMO参考资料背景 项目中,为测试算法的鲁棒性,经常会用到fuzz测试进行压力测试。fuzz测试是一种模糊测试方法,本质是通过灌入各种变异的随机数据,去遍历不同函数分支…...
电子标签拣货系统——外接供电版
Power_DC24v 型号:Power_DC24v24V电源适配器级联线:长30cm直径:15mmCK_Wire_V1 型号:CK_Wire_V1连接电源适配器级联线:长30cm公线:长宽厚 14*11*9mm母线:长宽厚 13*5.5*3mmCK_Wire_V2 型号&…...
为什么启动一个线程不用run()方法,而是用start()方法
在使用java多线程时,有三种方式创建线程 复习传送门 当使用继承Thread来实现多线程时, 我们会把线程执行的代码写在run() 方法中, 使用Thread的start()方法来启动一个线程。 代码如下: public class ThreadDemo extends Thread{O…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
