数据结构——第二章 线性表(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…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...

GraphRAG优化新思路-开源的ROGRAG框架
目前的如微软开源的GraphRAG的工作流程都较为复杂,难以孤立地评估各个组件的贡献,传统的检索方法在处理复杂推理任务时可能不够有效,特别是在需要理解实体间关系或多跳知识的情况下。先说结论,看完后感觉这个框架性能上不会比Grap…...
6.计算机网络核心知识点精要手册
计算机网络核心知识点精要手册 1.协议基础篇 网络协议三要素 语法:数据与控制信息的结构或格式,如同语言中的语法规则语义:控制信息的具体含义和响应方式,规定通信双方"说什么"同步:事件执行的顺序与时序…...
Docker、Wsl 打包迁移环境
电脑需要开启wsl2 可以使用wsl -v 查看当前的版本 wsl -v WSL 版本: 2.2.4.0 内核版本: 5.15.153.1-2 WSLg 版本: 1.0.61 MSRDC 版本: 1.2.5326 Direct3D 版本: 1.611.1-81528511 DXCore 版本: 10.0.2609…...

云原生时代的系统设计:架构转型的战略支点
📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、云原生的崛起:技术趋势与现实需求的交汇 随着企业业务的互联网化、全球化、智能化持续加深,传统的 I…...

关于 ffmpeg设置摄像头报错“Could not set video options” 的解决方法
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/148515355 长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV…...
分布式计算框架学习笔记
一、🌐 为什么需要分布式计算框架? 资源受限:单台机器 CPU/GPU 内存有限。 任务复杂:模型训练、数据处理、仿真并发等任务耗时严重。 并行优化:通过任务拆分和并行执行提升效率。 可扩展部署:适配从本地…...