数据结构--单链表实现

                                                        欢迎光顾我的homepage

前言        

        链表和顺序表都是线性表的一种,但是顺序表在物理结构和逻辑结构上都是连续的,但链表在逻辑结构上是连续的,而在物理结构上不一定连续;来看以下图片来认识链表与顺序表的差别

这里以动态顺序表为例,和链表中的单链表对比一下

动态顺序表

单链表

        这里就可以很清晰的看到顺序表的底层其实就是一个数组,数据的是连续存储的(顺序表物理结构连续);而链表它每一个数据都不是连续的(链表物理结构上不一定连续)。

链表节点

        通过观察上图,我们会发现链表每一个节点都存放在数据和下一个节点的地址。

        那么来想一下,为了链表每一个节点都统一起来,都存储有效数据和下一个节点的地址,我们就需要创建应该结构体,来存储有效数据和下一个节点的指针;
注:这里只是单链表

typedef int SLType;
typedef struct SLTNode
{SLType data;struct SLTNode* next;
}SLT;

创建好链表节点,接下来就来实习单链表存储数据的这些功能。

单链表实现

先来看一下单链表都实现都哪些功能

//输出链表
void SLTPrint(SLT* phead);
//创建节点
SLT* SLTCreat(SLType x);
//单链表头插
void SLTPushFront(SLT** pphead, SLType x);
//单链表尾插
void SLTPushBack(SLT** pphead, SLType x);
//单链表头删
void SLTPopFront(SLT** pphead);
//单链表尾删
void SLTPopBack(SLT** pphead);
//查找数据
SLT* SLTFind(SLT* phead, SLType x);
//指定位置之前插入
void SLTInsert(SLT** pphead, SLT* pos, SLType x);
//指定位置之后插入
void SLTInsertAfter(SLT* pos, SLType x);
//删除指定节点
void SLTErase(SLT** pphead, SLT* pos);
//删除指定位置后一个节点
void SLTEraseAfter(SLT* pos);

创建节点

        这里创建节点,还是使用动态内存来创建,创建完成后,将数据存储进去,并把新节点的下一个节点置为NULL

代码如下:

//创建节点
SLT* SLTCreat(SLType x)
{SLT* newnode = (SLT*)malloc(sizeof(SLT));assert(newnode);newnode->data = x;newnode->next = NULL;return newnode;
}

        测试一下代码是否正确

可以看到代码没有问题。

输出链表

        由于这里实现单链表,存储的是整型数据,就以整型的方式输出,若存储其他类型的数据,就以存储类型的方式输出。

输出链表,首先就要遍历链表,因为链表最后一个节点里存储的下一个节点的地址为空(即最后一个节点  ->next 为空)所以,遍历链表结束的条件就是ptail ==NULL,没输出一个就让ptail指向下一个节点,直到为空,遍历结束。

        来写代码实现:

//输出链表
void SLTPrint(SLT* phead)
{SLT* ptail = phead;while (ptail!= NULL)//也可以直接写成 ptail{printf("%d -> ", ptail->data);ptail = ptail->next;}printf("NULL\n");
}

这里先创建两个节点并存储数据输出看一下结果

int main()
{SLT* plist = SLTCreat(1);plist->next = SLTCreat(2);SLTPrint(plist);return 0;
}

        这里也成功输出插入的两个数据。(最后一个节点的next指向空)

单链表头插

        在链表头部插入数据,不用像顺序表那样去移动所以的有效数据,链表只需要改变一个指针的指向即可

假设现在链表中已经存在两个数据,再进行头插,这时就只需要改变plist的指向即可,当然新节点的next指针也要指向下一个节点(plist指向的节点)。

代码如下:

//单链表头插
void SLTPushFront(SLT** pphead, SLType x)
{assert(pphead);SLT* newnode = SLTCreat(x);newnode->next = *pphead;*pphead = newnode;
}

还有一种情况,如果现在链表中没有数据,再进行头插,这里代码也能实现链表没有数据时的头插

单链表尾插

        链表的尾插,因为指针传的是指向头节点的指针的地址,所以,我们需要先遍历链表,找到链表的尾部,再修改尾节点的next指针指向。

假设现在链表中已经存在两个数据,再进行尾插,此时我们只需要找到链表的尾部,修改尾节点next指针指向即可,代码如下

//单链表尾插
void SLTPushBack(SLT** pphead, SLType x)
{assert(pphead);SLT* newnode = SLTCreat(x);SLT* ptail = *pphead;//遍历链表while (ptail->next){ptail = ptail->next;}ptail->next = newnode;
}

        考虑了这种情况,再来看以下链表为空的情况,如果链表为空,这里ptail->next就对空指针进行解引用操作了;所以,我们需要判断链表是否为空?如果为空,插入的新节点就是头节点,直接让plist(即*pphead)指向新节点即可;如果不为空就正常进行尾插。

最终代码如下:

//单链表尾插
void SLTPushBack(SLT** pphead, SLType x)
{	assert(pphead);SLT* newnode = SLTCreat(x);if (*pphead == NULL) //判断链表是否为空{*pphead = newnode;}else {SLT* ptail = *pphead;//遍历链表while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}
}

这里代码可以正常进行尾插。

单链表头删

        链表头删,首先我们要判断链表是否为空,如果为空(空链表没有数据该如何删除呢?

链表头删,我们需要修改plist(*pphead)指向链表的下一个节点即头节点的next指针。

        此外:因为我们的节点都是动态申请的内存,所以在删除时,需要把它释放掉。

思路很简单,写一下代码:
 

//单链表头删
void SLTPopFront(SLT** pphead)
{assert(pphead && *pphead);SLT* del = (*pphead);*pphead = (*pphead)->next;free(del);del = NULL;
}

再来看一个如果链表为空,又是啥结果呢?

现在链表已经为空,在执行一次头删代码

这里assert断言报错。

单链表尾删

        首先尾删与头删一样,链表不能为空。

        尾删与尾插也有共同之处,就是遍历链表,找到链表的尾节点。找到尾节点,删除尾节点;然后修改尾节点上一个节点的next指针为NULL;所以在遍历链表时就要多记录一个节点。

//单链表尾删
void SLTPopBack(SLT** pphead)
{assert(pphead && *pphead);SLT* ptail = *pphead;SLT* pcur = *pphead;//遍历链表while (ptail->next){pcur = ptail;ptail = ptail->next;}pcur->next = NULL;free(ptail);ptail  = NULL;
}

        在测试的时候我们会发现一个问题,如果链表只有一个节点,删除之后我们的plist指针并没有置为空,而我们的空间已经释放掉了,这是很危险的;所以这里我们先判断一下链表是否只有一个节点,如果是,我们释放完空间以后,将(*pphead)置为空,以免出现野指针的情况。

完善后代码:

//单链表尾删
void SLTPopBack(SLT** pphead)
{assert(pphead && *pphead);if ((*pphead)->next== NULL){free(*pphead);*pphead = NULL;}else {SLT* ptail = *pphead;SLT* pcur = *pphead;//遍历链表while (ptail->next){pcur = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;pcur->next = NULL;}
}

测试没有问题,代码能够完成尾删这个功能。

查找数据

        查找数据,遍历链表查找数据,如果找到就返回数据所在节点的地址,如果没有找到就返回NULL;

//查找数据
SLT* SLTFind(SLT* phead, SLType x)
{SLT* ptail = phead;while (ptail){if (ptail->data == x){return ptail;}ptail = ptail->next;}return NULL;
}

这里测试以下:

数据存在时

数据不存在时

指定节点之前插入

        在链表指定节点之前插入数据,我们还需要知道指定节点的前一个节点,所以仍然需要遍历链表,寻找指定节点pos前一个节点。

//指定位置之前插入
void SLTInsert(SLT** pphead, SLT* pos, SLType x)
{assert(pphead && *pphead);SLT* ptail = *pphead;if (ptail == pos)//头插{SLTPushFront(pphead, x);}else{SLT* newnode = SLTCreat(x);while (ptail->next != pos)//找到pos位置{ptail = ptail->next;}newnode->next = pos;ptail->next = newnode;}
}

当然,这里如果故意传NULL给pos,(这就没啥意义了)这里也可以写以下assert断言pos。

指定节点之后插入

        在指定节点之后插入数据,就不需要再进行遍历链表,这里直接插入即可。

//指定位置之后插入
void SLTInsertAfter(SLT* pos, SLType x)
{assert(pos);SLT* newnode = SLTCreat(x);newnode->next = pos->next;pos->next = newnode;
}

删除指定节点

        删除链表中的指定节点,我们需要这个节点的上一个节点,所以又需要遍历链表,找到pos上一个节点,修改pos->next指针指向。

代码如下:

//删除指定节点
void SLTErase(SLT** pphead, SLT* pos)
{//找到pos上一个节点SLT* ptail = *pphead;while (ptail->next != pos){ptail = ptail->next;}SLT* p = pos->next;free(pos);pos = NULL;ptail->next = p;
}

删除指定节点后一个节点

        删除链表指定节点后一个节点,因为pos就是删除节点的上一个节点,所以不需要遍历链表,直接删除即可。

//删除指定位置后一个节点
void SLTEraseAfter(SLT* pos)
{assert(pos->next);SLT* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}

这里如果传过来的就是链表的尾节点,那没办法删除后一个节点,所以断言pos->next;

代码总览

#include"SList.h"
//创建节点
SLT* SLTCreat(SLType x)
{SLT* newnode = (SLT*)malloc(sizeof(SLT));assert(newnode);newnode->data = x;newnode->next = NULL;return newnode;
}
//输出链表
void SLTPrint(SLT* phead)
{SLT* ptail = phead;while (ptail != NULL)//也可以直接写成 ptail{printf("%d -> ", ptail->data);ptail = ptail->next;}printf("NULL\n");
}
//单链表头插
void SLTPushFront(SLT** pphead, SLType x)
{assert(pphead);SLT* newnode = SLTCreat(x);newnode->next = *pphead;*pphead = newnode;
}
//单链表尾插
void SLTPushBack(SLT** pphead, SLType x)
{	assert(pphead);SLT* newnode = SLTCreat(x);if (*pphead == NULL) //判断链表是否为空{*pphead = newnode;}else {SLT* ptail = *pphead;//遍历链表while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}
}
//单链表头删
void SLTPopFront(SLT** pphead)
{assert(pphead && *pphead);SLT* del = (*pphead);*pphead = (*pphead)->next;free(del);del = NULL;
}
//单链表尾删
void SLTPopBack(SLT** pphead)
{assert(pphead && *pphead);if ((*pphead)->next== NULL){free(*pphead);*pphead = NULL;}else {SLT* ptail = *pphead;SLT* pcur = *pphead;//遍历链表while (ptail->next){pcur = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;pcur->next = NULL;}
}
//查找数据
SLT* SLTFind(SLT* phead, SLType x)
{SLT* ptail = phead;while (ptail){if (ptail->data == x){return ptail;}ptail = ptail->next;}return NULL;
}
//指定位置之前插入
void SLTInsert(SLT** pphead, SLT* pos, SLType x)
{assert(pphead && *pphead);SLT* ptail = *pphead;if (ptail == pos)//头插{SLTPushFront(pphead, x);}else{SLT* newnode = SLTCreat(x);while (ptail->next != pos)//找到pos位置{ptail = ptail->next;}newnode->next = pos;ptail->next = newnode;}
}
//指定位置之后插入
void SLTInsertAfter(SLT* pos, SLType x)
{assert(pos);SLT* newnode = SLTCreat(x);newnode->next = pos->next;pos->next = newnode;
}
//删除指定节点
void SLTErase(SLT** pphead, SLT* pos)
{//找到pos上一个节点SLT* ptail = *pphead;while (ptail->next != pos){ptail = ptail->next;}SLT* p = pos->next;free(pos);pos = NULL;ptail->next = p;
}
//删除指定位置后一个节点
void SLTEraseAfter(SLT* pos)
{assert(pos->next);SLT* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}

感谢各位大佬支持并指出问题,

如果本篇内容对你有帮助,可以一键三连支持以下,感谢支持!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/40656.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WGAN(Wassertein GAN)

WGAN E x ∼ P g [ log ⁡ ( 1 − D ( x ) ) ] E x ∼ P g [ − log ⁡ D ( x ) ] \begin{aligned} & \mathbb{E}_{x \sim P_g}[\log (1-D(x))] \\ & \mathbb{E}_{x \sim P_g}[-\log D(x)] \end{aligned} ​Ex∼Pg​​[log(1−D(x))]Ex∼Pg​​[−logD(x)]​ 原始 GAN …

springboot基于Java的超市进销存系统+ LW+ PPT+源码+讲解

第三章系统分析与设计 3.1 可行性分析 一个完整的系统,可行性分析是必须要有的,因为他关系到系统生存问题,对开发的意义进行分析,能否通过本网站来补充线下超市进销存管理模式中的缺限,去解决其中的不足等&#xff0c…

6域名系统DNS

《计算机网络》第7版,谢希仁 每次记不清楚的知识点,通过上网查找,总是只能看到很零碎的答案。最后还是最喜欢看这个版本的书,一看就回忆起来了,逻辑严谨,循循善诱,知识讲解的全面又清晰&#xf…

架构师应该在团队中发挥怎样的作用?

架构师分为5种: 1.企业架构师EA(Enterprise Architect) EA的职责是决定整个公司的技术路线和技术发展方向。 2.基础结构架构师IA(Infrastructure Architect) IA的工作就是提炼和优化技术方面积累和沉淀形成的基础性的、公共的、可复用的框架和组件,这…

Qt 基础组件速学 鼠标和键盘事件

学习目标: 鼠标事件和键盘事件应用 前置环境 运行环境:qt creator 4.12 学习内容和效果演示: 1.鼠标事件 根据鼠标的坐标位置,做出对应的事件。 2.键盘事件 根据键盘的输入做出对应操作 详细主要代码 1.鼠标事件 #include "main…

一文读懂轻量日志收集系统Loki工作原理

Loki 是由 Grafana Labs 开发的日志聚合系统,设计目标是提供一种高效、低成本的日志收集和查询解决方案。与传统的日志系统(如 ELK Stack)不同,Loki 不会对日志内容进行索引,而是仅对日志的元数据进行索引,…

FTP、http 、tcp

HTTP VS FTP HTTP :HyperText Transfer Protocol 超文本传输协议,是基于TCP协议 FTP: File Transfer Protocol 文件传输协议, 基于TCP协议, 基于UDP协议的FTP 叫做 TFTP HTTP 协议 通过一个SOCKET连接传输依次会话数…

FIND_IN_SET使用案例--[sql语句根据多ids筛选出对应数据]

一 FIND_IN_SET select id,system_ids from intellect_client_info where FIND_IN_SET(5, system_ids) > 0;

Spring Boot 中的监视器是什么?有什么作用?

前言: 监听器相信熟悉 Spring、Spring Boot 的都知道,但是监视器又是什么?估计很多人一脸懵的状态,本篇分享一下 Spring Boot 的监视器。 Spring Boot 系列文章传送门 Spring Boot 启动流程源码分析(2) …

Apache DolphinScheduler 与 AWS 的 EMR/Redshift 集成实践分享

引言 这篇文章将给大家讲解关于DolphinScheduler与AWS的EMR和Redshift的集成实践,通过本文希望大家能更深入地了解AWS智能湖仓架构,以及DolphinScheduler在实际应用中的重要性。 AWS智能湖仓架构 首先,我们来看一下AWS经典的智能湖仓架构图…

高考选专业,兴趣与就业前景该如何平衡?

从高考结束的那一刻开始,有些家长和学生就已经变得焦虑了,因为他们不知道成绩出来的时候学生应该如何填报志愿,也不知道选择什么样的专业,毕竟大学里面的专业丰富多彩,如何选择确实是一门学问,而对于学生们…

乐清网站建设规划书

乐清是位于浙江省温州市的一个县级市,拥有悠久的历史和丰富的文化底蕴。随着互联网的快速发展,网站建设成为推动乐清经济和文化发展的重要手段。因此,我们认为有必要制定一个全面的乐清网站建设规划书,以促进乐清的经济繁荣和文化…

东芝 TB5128FTG 强大性能的步进电机驱动器

TB5128FTG它以高精度和高效能为设计理念,采用 PWM 斩波方法,并内置时钟解码器。通过先进的 BiCD 工艺制造,这款驱动器提供高达 50V 和 5.0A 的输出额定值,成为广泛应用场景中的强劲解决方案。 主要特性 TB5128FTG 拥有众多确保高…

SAP PS学习笔记01 - PS概述,创建Project和WBS

本章开始学习PS(Project System)。 1,PS的概述 PS(Project System)是SAP企业资源规划系统中的一个关键模块,主要用于项目管理。 它提供了一个全面的框架来规划、控制和执行项目,涵盖了从项目启…

竞赛选题 卷积神经网络手写字符识别 - 深度学习

文章目录 0 前言1 简介2 LeNet-5 模型的介绍2.1 结构解析2.2 C1层2.3 S2层S2层和C3层连接 2.4 F6与C5层 3 写数字识别算法模型的构建3.1 输入层设计3.2 激活函数的选取3.3 卷积层设计3.4 降采样层3.5 输出层设计 4 网络模型的总体结构5 部分实现代码6 在线手写识别7 最后 0 前言…

C++内存的一些知识点

一、内存分区 在C中,内存主要分为以下几个区域: 代码区:存放函数体的二进制代码。 全局/静态存储区:存放全局变量和静态变量,这些变量在程序的整个运行期间都存在。常量存储区:存放常量,这些值…

WPF UI 界面布局 魔术棒 文字笔记识别 技能提升 布局功能扩展与自定义 继承Panel的对象,测量与排列 系列七

应用开发第一步 功能分类:页面上的功能区域划分。。。。需求分析 业务逻辑 数据流 功能模块 UI/UX 编码 测试 发布 功能开发与布局 不用显式的方式设定元素的尺寸 不使用屏幕坐标来指定位置 Grid 功能最强大,布局最灵活的容器…

开发个人Go-ChatGPT--5 模型管理 (一)

开发个人Go-ChatGP–5 模型管理 (一) 背景 开发一个chatGPT的网站,后端服务如何实现与大模型的对话?是整个项目中开发困难较大的点。 如何实现上图的聊天对话功能?在开发后端的时候,如何实现stream的响应呢?本文就…

Vue-Router4.0 报“Cannot read property ‘forEach‘ of undefined”

Vue-Router4.0在创建路由时 报“Cannot read property ‘forEach‘ of undefined” 解决办法 将路由规则名称更改为routes,否则报错 import { createWebHashHistory, createRouter } from vue-router; // 创建路由规定 const routes [{path: /login,name: login,co…

【Unity数据交互】Unity中使用二进制进行数据持久化

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 专栏交流🧧&…