数据结构--单链表实现
欢迎光顾我的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;
}
感谢各位大佬支持并指出问题,
如果本篇内容对你有帮助,可以一键三连支持以下,感谢支持!!!

相关文章:
数据结构--单链表实现
欢迎光顾我的homepage 前言 链表和顺序表都是线性表的一种,但是顺序表在物理结构和逻辑结构上都是连续的,但链表在逻辑结构上是连续的,而在物理结构上不一定连续;来看以下图片来认识链表与顺序表的差别 这里以动态顺序表…...
2024攻防演练:亚信安全推出MSS/SaaS短期定制服务
随着2024年攻防演练周期延长的消息不断传出,各参与方将面临前所未有的挑战。面对强大的攻击队伍和日益严格的监管压力,防守单位必须提前进行全面而周密的准备和部署。为应对这一形势,亚信安全特别推出了为期三个月的MSS/SaaS短期订阅方案。该…...
基于java+springboot+vue实现的在线课程管理系统(文末源码+Lw)236
摘要 本文首先介绍了在线课程管理系统的现状及开发背景,然后论述了系统的设计目标、系统需求、总体设计方案以及系统的详细设计和实现,最后对在线课程管理系统进行了系统检测并提出了还需要改进的问题。本系统能够实现教师管理,科目管理&…...
每日一更 EFK日志分析系统
需要docker和docker-compose环境 下面时docker-compose.yaml文件 [rootnode1 docker-EFK]# cat docker-compose.yaml version: 3.3services:elasticsearch:image: "docker.elastic.co/elasticsearch/elasticsearch:7.17.5"container_name: elasticsearchrestart: …...
python类继承和类变量
Python一些类继承和实例变量的使用 定义基类 class APIException:code 500msg "Sorry, error"error_code 999def __init__(self, msgNone):print("APIException init ...")def error_400(self):pass复用基类的属性值 class ClientTypeError(APIExcept…...
js 随机生成整数
随机生成一个唯一的整数 id export const randomId () > { return Date.now() Math.floor(Math.random() * 10000) } 生成随机ID的方法 // 随机生成0 - 9999 export const randomId ()> { return Math.floor(Math.random() * 10000).toString() } // 随机生成0-999之…...
深入Django(七)
Django的数据库迁移系统 引言 在前六天的教程中,我们介绍了Django的基本概念、模型、视图、模板、URL路由和表单系统。今天,我们将讨论Django的数据库迁移系统,它是管理和跟踪数据库变化的关键组件。 Django数据库迁移概述 Django的数据库…...
【区分vue2和vue3下的element UI Steps 步骤条组件,分别详细介绍属性,事件,方法如何使用,并举例】
在 Vue 2 和 Vue 3 中,Element UI(针对 Vue 2)和 Element Plus(针对 Vue 3)提供了 Steps 步骤条组件,用于展示当前操作的进度步骤。虽然这两个库都提供了步骤条组件,但它们在属性、事件和方法的…...
uni-app x 跨平台开发框架
目录 uni-app x 是什么 和Flutter对比 uts语言 uvue渲染引擎 组合式API的写法 选项式API写法 页面生命周期 API pages.json全局配置文件 总结 uni-app x 是什么 uni-app x,是下一代 uni-app,是一个跨平台应用开发引擎。 uni-app x 是一个庞…...
YOLOv8模型调参---数据增强
目录 1.数据预处理 2.数据增强 2.1 数据增强的作用 2.2 数据增强方式与适用场景 2.2.1离线增强(Offline Augmentation) 2.2.2 在线增强(Online Augmentation) 3. 数据增强的具体方法 4. YOLOv8的数据增强 4.1 YOLOv8默认…...
【Nginx】docker运行Nginx及配置
Nginx镜像的获取 直接从Docker Hub拉取Nginx镜像通过Dockerfile构建Nginx镜像后拉取 二者区别 主要区别在于定制化程度和构建过程的控制: 直接拉取Nginx镜像: 简便性:直接使用docker pull nginx命令可以快速拉取官方的Nginx镜像。这个过程…...
tensorflow和numpy的版本
查看cuda版本 dpkg -l | grep cuda i libcudart11.0:amd64 11.5.117~11.5.1-1ubuntu1 amd64 NVIDIA CUDA Runtime Library ii nvidia-cuda-dev:amd64 11.5.1-1ubuntu1 …...
二维Gamma分布的激光点云去噪
目录 1、Gamma 分布简介2、实现步骤 1、Gamma 分布简介 Gamma 分布在合成孔径雷达( Synthetic Aperture Radar,SAR) 图像分割中具有广泛应用,较好的解决了SAR 图像中相干斑噪声对图像分割的影响。采用二维Gamma 分布对…...
鸿蒙笔记导航栏,路由,还有axios
1.导航组件 导航栏位置可以调整,导航栏位置 Entry Component struct t1 {build() {Tabs(){TabContent() {Text(qwer)}.tabBar("首页")TabContent() {Text(发现内容)}.tabBar(发现)TabContent() {Text(我的内容)}.tabBar("我的")}// 做平板适配…...
Spring 框架中都用到了哪些设计模式:单例模式、策略模式、代理模式
Spring 框架是一个功能强大的企业级应用开发框架,它使用了多种设计模式来提高代码的可维护性、可扩展性和可重用性。以下是 Spring 框架中常见的几个设计模式,并简要说明它们的应用场景: 1. 单例模式(Singleton Pattern) 定义:确保一个类只有一个实例,并提供全局访问点…...
阶段总结——基于深度学习的三叶青图像识别
阶段总结——基于深度学习的三叶青图像识别 文章目录 一、计算机视觉图像分类系统设计二、训练模型2.1. 构建数据集2.2. 网络模型选择2.3. 图像数据增强与调参2.4. 部署模型到web端2.5. 开发图像识别小程序 三、实验结果3.1. 模型训练3.2. 模型部署 四、讨论五、参考文献&#…...
深度解析Java世界中的对象镜像:浅拷贝与深拷贝的奥秘与应用
在Java编程的浩瀚宇宙中,对象拷贝是一项既基础又至关重要的技术。它直接关系到程序的性能、资源管理及数据安全性。然而,提及对象拷贝,不得不深入探讨其两大核心类型:浅拷贝(Shallow Copy)与深拷贝…...
Python | Leetcode Python题解之第218题天际线问题
题目: 题解: class Solution:def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:buildings.sort(keylambda bu:(bu[0],-bu[2],bu[1]))buildings.append([inf,inf,inf])heap [[-inf,-inf,-inf]]ans []for l,r,h in buildings:i…...
使用Spring Boot构建RESTful API
使用Spring Boot构建RESTful API 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天,我们将深入探讨如何使用Spring Boot构建RESTful API。通过这篇…...
Spark快速大数据分析PDF下载读书分享推荐
《Spark 快速大数据分析》是一本为 Spark 初学者准备的书,它没有过多深入实现细节,而是更多关注上层用户的具体用法。不过,本书绝不仅仅限于 Spark 的用法,它对 Spark 的核心概念和基本原理也有较为全面的介绍,让读者能…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...
