数据结构(链表)

9f28ba058efc459681a76e553f453b87.png

  🌏个人博客主页:心.c

2690b34b273145b9ae50a55ae78c53a4.gif#pic_center

前言: 最近练习算法回去学了链表,收获挺大的,大概内容整理了一下,语言是用c写的,所以在这里分享给大家,希望大家可以有所收获 

🔥🔥🔥文章专题:链表 

😽感谢大家的点赞👍收藏⭐️评论✍您的一键三连是我更新的动力 💓 


b333ab1b28b447a8a491bd89340ea770.png

链表的概念: 

链表是一种线性数据结构,它不像数组那样在内存中连续存储数据,而是通过节点(每个节点包含数据和指向下一个节点的指针)链接起来形成一个序列。每个元素不仅包含数据,还包含对下一个元素的引用,这种设计允许链表在内存中以灵活和动态的方式分布。

  • 节点: 链表的基本组成单位。每个节点包含数据和一个指向下一个节点的指针。
  • 头结点: 列表中的第一个节点。
  • 尾节点: 列表中的最后一个节点,其指针通常为NULL
  • 指针: 每个节点都有一个指针,用于存储下一个节点的地址。在单向链表中,节点仅指向其后继;在双向链表中,节点还包含一个指向前驱的指针。

 链表的定义:

 链表是一种线性数据结构,链表的元素(通常称为节点)可以在内存的任意位置分散存储。链表中的每个节点包含两部分:一部分是用于存储数据的字段,另一部分是一个指针

//定义节点内容的数据类型
typedef int SLTDateType;
typedef struct SListNode {SLTDateType data;struct SLTNode* next;
}SLTNode;

 下面这个是我对链表的理解与感悟,如果写的有问题希望大佬们可以指出

 b7ce4b81727149d595f39756fa9deff9.png

SLTNode *phead = NULL;

在这个例子中,phead是一个指向SListNode结构体的指针,它是链表的起点。我们通过这个指针来访问和操作链表中的节点。

链表本身是由一系列节点组成的,而我们用来管理和操作链表的主要工具是一个指向链表头结点的指针。这个指针是链表的入口点,通过它可以访问链表中的所有节点。所以,当我们说“链表本身是一个指针”,我们实际上是在指这个头结点指针。但要理解,这个指针指向的是一个结构体(链表的头结点),而链表的全部内容是由多个这样的结构体节点通过指针链接起来的。

链表节点的创建:

因为在进行链表的添加的时候,我们要不停向添加的项创建节点,所以在这里我就写一个创建节点的方法,下面在添加节点的时候就不需要在进行一系列的判断和创建动态内存和添加了

因为malloc创建内存可能会失败,所以我们要加判断语句 if (newNode)来进行判断,如果添加节点newNode成功,我们就将节点newNode所指向的地方赋值,并且返回所创建的节点

//创建链表
SLTNode* SLTBuyNode(SLTDateType x) {SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));//如果创建链表失败if (newNode) {perror("malloc fail");exit(1);}//如果创建成功newNode->data = x;newNode->next = NULL;return newNode;}

 链表的插入:

讲节点的插入和删除讲一下二级指针的概念和在下文中的作用 

!!!传一级指针,我们需要传入一级指针的地址,并把二级指针当作形参来进行传递一级指针的地址,传指针必须传地址!!! 

下文中关于进行节点的删除和添加方法的参数几乎都是二级指针传参,因为在这里我们的返回值是void,我们的节点是一个结构体指针,所以我们要传二级指针来来表示一级指针的地址来传递我们节点本身的地址,也就是我们结构体指针的地址如果我们传的是一级指针,那么我们传的就不是地址而是值,如果传值返回值为void那么我们的值就不会发生变化方法中所进行的改变也只是在指针的副本中,并不会对节点产生本质改变

int main() {SLTNode* plist = NULL;//插入节点SLTPushBack(&plist, 1);//打印节点SLTPrint(plist);
}

第一个节点的内容 *plist

指向第一个节点的指针(就是我们的结构体指针): plist

指向第一个节点的指针的地址&plist

尾部插入节点:

*pphead 是我们的结构体指针   pphead是指向我们结构体指针的指针(也就是二级指针)

插入时先判断参数是否为NULL,如果为空,就停止程序,如果第一个结构体指针的内容为NULL(是否存放地址),就直接将创建新节点的内容赋值到第一个结构体指针中,如果第一个结构体指针内容不为空,那么就通过while判断找到最后一个结构体指针,然后将最后一个结构体指针内容中的next指向我们新创建的节点的内容因为如果第一个结构体指针内容为NULL的话就不能对ptail->next = newNode;进行判断了,否则会报错,所以我们要分开写

//尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x) {//判断是否存在链表(传入是否为NULL)assert(pphead);//创建链表SLTNode* newNode = SLTBuyNode(x);//如果指向第一个节点的指针为空if (*pphead==NULL) {*pphead = newNode;}else {SLTNode* ptail = *pphead;while (ptail->next) {ptail = ptail->next;}ptail->next = newNode;}
}
头部插入节点:

在头部插入节点比较简单,也是先判断传参是否为NULL,如果不为NULL,那么将第一个节点所指向的内容赋值给我们的新节点,然后将我们创建的节点内容指向我们的第一个节点,并且将第一个节点改为我们创建的新节点

//头插
void SLTPushFront(SLTNode** pphead, SLTDateType x) {assert(pphead);SLTNode* newNode = SLTBuyNode(x);newNode->next = *pphead;//将头结点赋给新创建的节点*pphead = newNode;
}
 在指定位置之前插入节点: 

在插入节点之前先保证我们的链表和节点不为空,还要保证我们的节点pos不为空,处理完这些我们就要进行分析了,如果我们想实现在pos之前插入数据,我们要得到pos之前的节点,然后进行插入,但是当我们的pos是我们的第一个节点,那么我们就不需要找到之前的节点然后进行插入了,可以直接执行我们头插的方法了


//在指定位置之前插入节点
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x) {//判断链表是否为空assert(pphead&&*pphead);assert(pos);//创建新节点SLTNode* newNode = SLTBuyNode(x);//如果为头插if (*pphead == pos) {SLTPushFront(pphead, x);}else {//找到pos之前的节点SLTNode* prev = *pphead;while (prev != pos) {prev = prev->next;}newNode->next = pos;prev->next = newNode;}
}
在指定位置之后插入节点:

当在指定位置插入节点时,我们就不需要头结点pphead了,我们只需要找到我们的pos节点,然后直接进行插入就可以了

//在指定位置之后插入节点
void SLTInsertAfter(SLTNode* pos, SLTDateType x) {assert(pos);SLTNode* newNode = SLTBuyNode(x);newNode->next = pos->next;pos->next = newNode;
}

 链表的删除:

尾部删除节点:

删除节点比较简单,分两种情况,第一个就是多个链表,我们在这里只需要找到最后两个节点,然后将最后最后一个节点释放,然后赋值为NULL,然后将倒数第二个节点的内容next指向的地址也赋值为NULL,防止野指针的出现,但是还要一个情况,当我们的链表只有一个节点时我们根本不需要上面那个方法了,只需要将第一个节点也就是我们的最后一个节点删除掉,然后将结构体指针赋值为NULL就可以了


//尾删
void SLTPopBack(SLTNode** pphead) {//链表不为空也不指向空assert(pphead && *pphead);if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;}else {//定义倒数第二个节点SLTNode* prev = *pphead;//定义最后一个节点SLTNode* ptail = *pphead;while (ptail->next) {prev = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;prev->next = NULL;}
}
头部删除节点:

头删链表比尾删链表简单的多,这里我们不需要判断那么多情况,在这里我们只需要获得第二个节点,然后将第一个节点释放掉,赋值为NULL,然后将我们的第一个结构体指针内容换成我们的第二个节点,第二个节点是否为NULL无所谓 ,不会影响后续添加

//头删
void SLTPopFront(SLTNode** pphead) {assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}
删除pos节点:

 当我们删除pos节点我们需要分两种情况,第一种就是多个节点,我们正常获取pos之前的节点,然后删除一个节点,将前一个节点连接我们的后一个节点,还要一种就是当我们只有一个节点时,将直接使用头删方法

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos) {assert(pphead && *pphead);assert(pos);//如果节点是第一个if (pos == *pphead) {SLTPopFront(pphead);}else {SLTNode* prev = *pphead;while (prev->next != pos) {prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}}
删除pos之后的节点:

删除pos之后的节点也很简单,这里我们就需要获得pos节点,然后在获得pos之后要删除的节点,然后将我们要删的节点的下一个节点赋值给我们pos节点的next,然后进行删除赋值 

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos) {assert(pos&&pos->next);//获取pos后面的一个节点SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

 给大家补充一个方法就是打印链表的方法


//打印链表
void SLTPrint(SLTNode* phead) {SLTNode* pcur = phead;while (pcur) {printf("%d", pcur->data);pcur = pcur->next;}printf("NULL\n");
}

讲到这里就结束了,谢谢大家的观看

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

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

相关文章

2024年技校大数据实验室建设及大数据实训平台整体解决方案

随着信息技术的迅猛发展,大数据已成为推动产业升级和社会进步的重要力量。为适应市场需求,培养高素质的大数据技术人才,技校作为职业教育的重要阵地,亟需加强大数据实验室的建设与实训平台的打造。本方案旨在提出一套全面、可行的…

Synchronized关键字和乐观锁(CAS)

一、Sychronized关键字 在Java中,synchronized 是一个关键字,用于实现线程同步。当一个方法或一个代码块被synchronized修饰时,它被称为同步方法或同步代码块。这意味着每次只有一个线程可以进入该方法或代码块,其他线程必…

二维码的生成与识别(python)

二维码生成 from PIL import Image import qrcode from qrcode.image.styledpil import StyledPilImage from qrcode.image.styles.colormasks import SolidFillColorMask from qrcode.image.styles.moduledrawers import SquareModuleDrawer# 创建二维码对象 qr qrcode.QRCo…

Windows系统笔记本无法连接Wi-Fi常见原因及解决办法

在现代生活中,Wi-Fi已成为我们连接互联网不可或缺的方式之一。 然而,有时我们的Windows系统笔记本可能会遇到无法连接Wi-Fi的问题。 这种情况可能由多种原因引起,包括硬件故障、驱动问题、系统设置等。 以下是针对Windows 10和Windows 11系…

【STM32】stm32中GPIO_ReadInputDataBit()是什么意思

GPIO_ReadInputDataBit()函数用于读取指定GPIO端口的某一引脚上的电平状态,并返回该引脚的电平是高电平(1)还是低电平(0)。 在STM32单片机中,GPIO(General-Purpose Input/Output)端…

vue3在元素上绑定自定义事件弹出虚拟键盘

最近开发中遇到一个需求: 焊接机器人的屏幕上集成web前端网页, 但是没有接入键盘。这就需要web端开发一个虚拟键盘,在网上找个很多虚拟键盘没有特别适合,索性自己写个简单的 图片: 代码: (代码可能比较垃圾冗余,也没时间优化,凑合看吧) 第一步:创建键盘组件 为了方便使用…

【Django】 读取excel文件并在前端以网页形式显示-安装使用Pandas

文章目录 安装pandas写views写urls安装openpyxl重新调试 安装pandas Pandas是一个基于NumPy的Python数据分析库,可以从各种文件格式如CSV、JSON、SQL、Excel等导入数据,并支持多种数据运算操作,如归并、再成形、选择等。 更换pip源 pip co…

Flink SQL 实时读取 kafka 数据写入 Clickhouse —— 日志处理(三)

文章目录 前言Clickhouse 表设计adlp_log_local 本地表adlp_log 分布式表 Flink SQL 说明创建 Source Table (Kafka) 连接器表创建 Sink Table (Clickhouse) 连接器解析 Message 写入 Sink 日志查询演示总结 前言 在之前的文章中,我们总结了如何在 Django 项目中进…

构建智慧水利系统,优化水资源管理:结合物联网、云计算等先进技术,打造全方位、高效的水利管理系统,实现水资源的最大化利用

本文关键词:智慧水利、智慧水利工程、智慧水利发展前景、智慧水利技术、智慧水利信息化系统、智慧水利解决方案、数字水利和智慧水利、数字水利工程、数字水利建设、数字水利概念、人水和协、智慧水库、智慧水库管理平台、智慧水库建设方案、智慧水库解决方案、智慧…

spring-boot3.x整合Swagger 3 (OpenAPI 3) +knife4j

1.简介 OpenAPI阶段的Swagger也被称为Swagger 3.0。在Swagger 2.0后,Swagger规范正式更名为OpenAPI规范,并且根据OpenAPI规范的版本号进行了更新。因此,Swagger 3.0对应的就是OpenAPI 3.0版本,它是Swagger在OpenAPI阶段推出的一个…

Unity判断鼠标是否在UI上

Unity判断鼠标是否在UI上 下值等于true表示在UI上 EventSystem.current.IsPointerOverGameObject()可用来判断滚轮滑动缩放视角功能,在UI上滑动滚轮视角不缩放,反之缩放。

Python开发日常总结

1、命令总结 1.1 conda创建、激活、退出虚拟环境 conda create --name myenv python3.8 # 创建 conda create --name myenv python3.9 # 激活 conda activate myenv # 退出 conda deactivate

产品系统的UI暗色系和浅色系模式切换是符合人体视觉工程学的设计

视觉革命:UI设计中的暗夜与黎明 UI设计如同夜空中最亮的星辰,引领着用户穿梭于信息的海洋。而今,一场视觉革命正在悄然上演,它关乎于我们的眼睛,关乎于我们的体验——那就是产品系统的UI暗色系和浅色系模式的切换。如…

手写一个JVM自定义类加载器

1. 自定义类加载器的意义 隔离加载类:在某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境。比如:阿里内某容器框架通过自定义类加载器确保应用中依赖的jar包不会影响到中间件运行时使用的jar包。再比如:Tomcat这类Web…

Android lmkd机制详解

目录 一、lmkd介绍 二、lmkd实现原理 2.1 工作原理图 2.2 初始化 2.3 oom_adj获取 2.4 监听psi事件及处理 2.5 进程选取与查杀 2.5.1 进程选取 2.5.2 进程查杀 三、关键系统属性 四、核心数据结构 五、代码时序 一、lmkd介绍 Android lmkd采用epoll方式监听linux内…

SpringBoot整合阿里云短信业务

详细介绍SpringBoot整合阿里云短信服务的每一步过程,同时会将验证码存放到Redis中并设置过期时间,尽量保证实战的同时也让没做过的好兄弟也能实现发短信的功能~ 1. 注册阿里云账号和创建Access Key 首先,你需要注册一个阿里云账号&#xff0…

Flutter 使用 url_launcher的canLaunchUrl() 方法总是返回false错误

Flutter 使用 url_launcher的canLaunchUrl() 方法总是返回false错误 众所周知,我们一般使用url_launcher来打开各种应用,网页,手机应用等.... 但是最近发现Flutter的canLaunchUrl()方法总是返回false,这是为什么呢? …

Qt 实战(3)数据类型 | 3.2、QVariant

文章目录 一、QVariant1、存储数据1.1、存储Qt内置数据1.2、存储自定义数据 2、获取数据3、判断数据类型4、清空数据5、总结 前言: QVariant是Qt框架中一个非常强大且灵活的类,它提供了一种通用的方式来存储和转换几乎任何类型的数据。无论是基本数据类型…

【JavaEE初阶】Thread类及常见方法

目录 📕 Thread类的概念 📕 Thread 的常见构造方法 📕 Thread 的几个常见属性 📕 start()-启动一个线程 📕 中断一个线程 🚩 实例一 🚩 实例二 🚩 实例三 📕 jo…

Android中的usescleartexttraffic属性详解

Android中的usescleartexttraffic属性详解 usesCleartextTraffic 是 Android 应用程序开发中的一个重要配置选项,用于控制应用程序是否允许通过不加密的 HTTP 协议进行网络通信。在 Android 应用的开发过程中,正确地配置 usesCleartextTraffic 对于保护用…