数据结构-单链表
1 链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
从以上图片可以看出:
1.链式结构在逻辑上是连续的,但在物理上不一定是连续的。
2.现实中的节点一般是在堆上申请出来的。
3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,可能不连续。
2 链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
2.1单向或双向
2.2带头或者不带头

2.3循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
3 单向无头链表的实现
在头文件中包含一些函数的声明。
因为每个节点都是一个结构体,所以每个节点都要存放一个结构体的指针,指向下一个节点。
typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;SLTNode* BuyListBNode(SLTDataType x);void PrintSList(SLTNode* phead);void SLTPushBcak(SLTNode** pphead,SLTDataType x);//尾插void SLTPushFront(SLTNode** pphead, SLTDataType x);//头插void SLTPopback(SLTNode** pphead);//尾删void SLTPopFront(SLTNode** pphead,SLTDataType x);//头删void SLTFind(SLTNode* pphead,SLTDataType x);//查找//在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//在pos之后插入x
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x);//删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos);//删除pos的后一个位置
void SLTEraseAfter(SLTNode* pos);
3.1打印链表
打印链表首先要遍历链表,那么循环的条件就是走到空。所以我们创建一个临时变量cur代替头节点用来遍历,这样就可以不用动头节点,打印就是将节点中的数据打印出来,所以先将各个节点的数据打印出来,再指向下一个节点,需要注意的是next就是下一个节点的地址,所以将cur->next赋给cur就可以拿到下一个节点的地址了,拿到地址就可以继续访问下一个节点了。
void PrintSList(SLTNode* phead)
{SLTNode* cur = phead;while (cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}
3.2创建节点
这里malloc一个节点出来就行了,然后判断是否malloc成功,将需要的数据存进data中就行了,然后将next置为NULL,然后返回这个节点。
SLTNode* BuyListBNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*) malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}
3.3尾插节点
这个函数的第一个参数是一个二级指针,目的是为了修改结构体,尾插节点首先需要创建一个节点,然后·判断一下当前链表是否为空,如果为空则将这个节点设置为头节点,所以解引用这个二级指针,拿到一级指针的地址,就可以修改了。如果不为空,则创建一个临时变量来保存头节点的地址,然后使用这个变量来遍历链表找到尾节点,循环的结束条件就是tail的next为空,因为尾节点的next是NULL,循环结束之后tail就走到了尾节点的位置,然后将新节点赋给tail的next即可。
void SLTPushBcak(SLTNode** pphead, SLTDataType x)
{SLTNode* newnode = BuyListBNode(x);if (*pphead == NULL){*pphead = newnode;}else{SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}
3.4头插节点
尾插节点还是需要创建一个节点,然后将这个节点的next指向这个头节点,但是头插之后头节点就是新插入的这个节点,所以需要使用二级指针,最后将新节点newnode赋给*pphead,这样头节点就更新了。
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{SLTNode* newnode = BuyListBNode(x);newnode->next = *pphead;*pphead = newnode;
}
3.4尾删节点
尾删需要分很多种情况:
1.没有节点,这种情况就直接暴力检查,没有节点是删除不了的,直接assert即可。
2.一个节点,如果是一个节点的话,这个节点的next一定是NULL,所以使用if判断*pphead->next是否为NULL,如果是的话直接free掉这个节点然后置为空就行了。
3.如果是多个节点的话创建一个临时变量来遍历链表找到尾,需要注意的是,这个循环的结束条件是tail->next->next为空。下面这段代码就是错误的,因为free(tail)的本质是将tail指向的节点free,再将tail置为空相当于给tail赋值0x00000000,局部变量出了作用域就销毁了,但是前一个节点还是野指针,next虽然还是指向这个节点,但是这个节点已经free了。所以解决的方法就是找到tail的前一个节点,然后free掉tail->next,再置空。
正确的方法:
void SLTPopback(SLTNode** pphead)
{assert(*pphead);//一个节点if ((*pphead)->next = NULL){free(*pphead);*pphead = NULL;}else//多个节点{SLTNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}
}
3.5头删节点
头删也需要用到二级指针,然后暴力检查链表是否为空,不为空则创建一个变量newnode来保存头节点的next,然后free掉头节点,再将newnode赋给*pphead。
void SLTPopFront(SLTNode** pphead)
{assert(pphead);SLTNode* newhead = (*pphead)->next;free(*pphead);*pphead = newhead;
}
3.6查找节点
这个函数很简单,找到data就行,然后返回节点。
SLTNode* SLTFind(SLTNode* pphead, SLTDataType x)
{SLTNode* cur = pphead;while (cur != NULL){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
3.6在pos位置之前插入节点
pos的位置也需要分情况:
1.暴力检查
2.如果是头插,直接调用头插函数。
3.正常情况,创建一个结构体变量来遍历链表寻找pos节点,但是循环的结束条件设置成pre->next=pos最合适,因为我们需要保存pos的前一个节点,所以循环结束后pre就是pos的前一个节点,此时创建一个需要插入的节点newnode,将pre的next指向newnode,再将newnode的next指向pos,就完成链接了。
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pos);if (*pphead == pos){SLTPopFront(pphead,x);}else{SLTNode* pre = *pphead;while (pre->next != pos){pre = pre->next;}SLTNode* newnode = BuyListBNode(x);pre->next = newnode;newnode->next = pos; }
}
3.7在pos位置之后插入节点
首先暴力检查,再创建一个新节点newnode,插入需要注意的是以下这种写法是错误的,因为当我们将pos的next指向newnode的时候,就与后面的节点完全断开了,然后newnode的next又指向pos的next相当于形成了一个死循环。正确的方法应该是pos的next指向newnode的next,相当于先将newnode的next指向pos的后一个节点形成newnode的尾部链接,再将pos的next指向newnode完成newnode的头部链接。
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuyListBNode(x);newnode->next = pos->next;pos->next = newnode;}
3.8删除pos位置的节点
首先暴力检查,再判断pos是不是头删,正常删除就是创建一个变量遍历链表,pos的next为空作为循环的结束条件,循环结束之后pre就是pos的前一个节点,这个时候将pre的next指向pos的next也就是pos的下一个节点就行了,然后frre掉pos,这时候就不需要置空了。
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pos);if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* pre = *pphead;while (pre->next != pos){pre = pre->next;}pre->next = pos->next;free(pos);}
}
3.9删除pos位置之后的节点
首先暴力检查,然后再检查是否为尾节点。创建一个变量posNext保存pos的下一个节点,然后即将pos的下一个节点指向pos的下下个节点即可。然后free掉posNext。
void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);//检查是否是尾节点SLTNode* posNext = pos->next;pos->next = posNext->next;free(posNext);
}
今天的分享到这里就结束啦!谢谢老铁们的阅读,让我们下期再见。
相关文章:

数据结构-单链表
1 链表的概念及结构 概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。 从以上图片可以看出: 1.链式结构在逻辑上是连续的,但在物理上不一定是连续的。 2.现实中的节…...

红队系列-IOT安全深入浅出
红队专题 设备安全概述物联网设备层次模型设备通信模型 渗透测试信息收集工具 实战分析漏洞切入点D-link 850L 未授权访问 2017 认证绕过认证绕过 D-link DCS-2530Ltenda 系列 路由器 前台未授权RTSP 服务未授权 访问 弱口令命令注入思科 路由器 固件二进制 漏洞 IoT漏洞-D-Lin…...

亚数受邀参加“长三角G60科创走廊量子密码应用创新联盟(中心)”启动仪式
11月8日,在第六届中国国际进口博览会2023长三角G60科创走廊高质量发展要素对接大会上,亚数信息科技(上海)有限公司CEO翟新元作为密码企业代表之一受邀参加“长三角G60科创走廊量子密码应用创新联盟(中心)”…...
直方图学习
直方图均衡化(Histogram Equalization)是一种用于增强图像对比度的图像处理技术,通过重新分配图像的像素值,使图像中的亮度级别更加均匀,以改善图像的视觉质量。下面是进行直方图均衡化的一般步骤: 计算原始…...

Java / Android 多线程和 synchroized 锁
s AsyncTask 在Android R中标注了废弃 synchronized 同步 Thread: thread.start() public synchronized void start() {/*** This method is not invoked for the main method thread or "system"* group threads created/set up by the VM. Any new functionali…...

基于51单片机的万年历-脉搏计仿真及源程序
一、系统方案 1、本设计采用51单片机作为主控器。 2、DS1302采集年月日时分秒送到液晶1602显示。 3、按键年月日时分秒,心率报警上下限。 4、红外对接管传感器采集心率送到液晶1602显示。 5、心率低于下限或高于上限,蜂鸣器报警。 二、硬件设计 原理图如…...

【ARFoundation学习笔记】点云与参考点
写在前面的话 本系列笔记旨在记录作者在学习Unity中的AR开发过程中需要记录的问题和知识点。主要目的是为了加深记忆。其中难免出现纰漏,更多详细内容请阅读原文以及官方文档。 汪老师博客 文章目录 点云新建点云 参考点参考点的工作原理何时使用参考点使用参考点…...

uni-app:js实现数组中的相关处理-数组复制
一、slice方法-浅拷贝 使用分析 创建一个原数组的浅拷贝,对新数组的修改不会影响到原数组slice() 方法创建了一个原数组的浅拷贝,这意味着新数组和原数组中的对象引用是相同的。因此,当你修改新数组中的对象时,原数组中相应位置的…...
8 STM32标准库函数 之 实时时钟(RTC)所有函数的介绍及使用
8 STM32标准库函数 之 实时时钟(RTC)所有函数的介绍及使用 1. 图片有格式2 文字无格式二、RTC库函数固件库函数预览2.1 函数RTC_ITConfig2.2 函数RTC_EnterConfigMode2.3 函数RTC_ExitConfigMode2.4 函数RTC_GetCounter.2.5 函数RTC_SetCounter2.6 函数RTC_SetPrescaler2.7 函…...

ARMday04(开发版简介、LED点灯)
开发版简介 开发板为stm32MP157AAA,附加一个拓展版 硬件相关基础知识 PCB PCB( Printed Circuit Board),中文名称为印制电路板,又称印刷线路板,是重要的电子部件,是电子元器件的支撑体,是电子…...
国际腾讯云:云服务器疑似被病毒入侵问题解决方案!!!
云服务器可能由于弱密码、开源组件漏洞的问题被黑客入侵,本文介绍如何判断云服务器是否被病毒入侵,及其解决方法。 问题定位 使用 SSH 方式 或 使用 VNC 方式 登录实例后,通过以下方式进行判断云服务器是否被病毒入侵: rc.loca…...

Perl语言用多线程爬取商品信息并做可视化处理
首先,我们需要使用Perl的LWP::UserAgent模块来发送HTTP请求。然后,我们可以使用HTML::TreeBuilder模块来解析HTML文档。在这个例子中,我们将使用BeautifulSoup模块来解析HTML文档。 #!/usr/bin/perl use strict; use warnings; use LWP::User…...

认识计算机-JavaEE初阶
文章目录 一、计算机的发展史二、冯诺依曼体系(Von Neumann Architecture)三、CPU基本工作流程3.1 算术逻辑单元(ALU)3.2 寄存器(Register)和内存(RAM)3.3 控制单元(CU)3…...
you-get - 使用代码下载视频
文章目录 关于 you-get代码调用报错处理 源码简单分析 关于 you-get github : https://github.com/soimort/you-get you-get 是一个有名的开源视频下载工具包,这里不赘述。 代码调用 you-get 提供了命令行的方式下载视频,这里介绍使用 Python 调用源代…...

【Proteus仿真】【51单片机】汽车尾灯控制设计
文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器,使用按键、LED模块等。 主要功能: 系统运行后,系统运行后,系统开始运行,K1键控制左转向灯;…...

浙大恩特客户资源管理系统任意文件上传漏洞复现
0x01 产品简介 浙大恩特客户资源管理系统是一款针对企业客户资源管理的软件产品。该系统旨在帮助企业高效地管理和利用客户资源,提升销售和市场营销的效果。 0x02 漏洞概述 浙大恩特客户资源管理系统中fileupload.jsp接口处存在文件上传漏洞,未经身份认…...

史上第一款AOSP开发的IDE (支持Java/Kotlin/C++/Jni/Native/Shell/Python)
ASFP Study 史上第一款AOSP开发的IDE (支持Java/Kotlin/C/Jni/Native/Shell/Python) 类似于Android Studio,可用于开发Android系统源码。 Android studio for platform,简称asfp(爱上富婆)。 背景&下载&使用 背景 由…...

GCC + Vscode 搭建 nRF52xxx 开发环境
在 Windows 下使用 GCC Vscode 搭建 nRF52xxx 开发环境 ...... by 矜辰所致前言 最近有遇到项目需求,需要使用到 Nordic 的 nRF52xxx 芯片,还记得当初刚开始写博文的时候的写的 nRF52832 学习笔记,现在看当时笔记毫无逻辑可言,…...

Linux应用开发基础知识——Framebuffer 应用编程(四)
前言: 在 Linux 系统中通过 Framebuffer 驱动程序来控制 LCD。Frame 是帧的意 思,buffer 是缓冲的意思,这意味着 Framebuffer 就是一块内存,里面保存着 一帧图像。Framebuffer 中保存着一帧图像的每一个像素颜色值,假设…...

智安网络|数据库入门秘籍:通俗易懂,轻松掌握与实践
在现代信息化时代,数据库已成为我们日常生活和工作中不可或缺的一部分。然而,对于非专业人士来说,数据库这个概念可能很抽象,难以理解。 一、什么是数据库? 简单来说,数据库是一个存储和管理数据的系统。它…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...

Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅!
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅! 🌱 前言:一棵树的浪漫,从数组开始说起 程序员的世界里,数组是最常见的基本结构之一,几乎每种语言、每种算法都少不了它。可你有没有想过,一组看似“线性排列”的有序数组,竟然可以**“长”成一棵平衡的二…...

在Zenodo下载文件 用到googlecolab googledrive
方法:Figshare/Zenodo上的数据/文件下载不下来?尝试利用Google Colab :https://zhuanlan.zhihu.com/p/1898503078782674027 参考: 通过Colab&谷歌云下载Figshare数据,超级实用!!࿰…...