【数据结构】单链表(一)
上一篇【数据结构】顺序表-CSDN博客 我们了解了顺序表,但是呢顺序表涉及到了一些问题,比如,中间/头部的插入/删除,时间复杂度为O(N);增容申请空间、拷贝、释放旧空间会有不小的消耗;增容所浪费的空间... 我们如何去解决这些问题?本篇介绍另一个数据结构——链表
1. 链表的概念及结构
链表:是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序实现的,链表也是线性表的一种
链表大概是什么样的呢?看下图
链表是由一个一个的节点组成,再顺序表中,数据是连续存放的,我们想找到下一个数据顺着前一个数据找就行了,而链表中数据存放空间是不连续的,所以我们就需要一个指针来保存下一个节点的地址,所以,我们如果要定义链表,只需要定义链表的节点结构就行了
(在【C语言】结构体详解-CSDN博客 的1.3 结构体的自引用 中介绍过)
struct SListNode
{int data;//数据struct SListNode* next;//下一个数据的地址
};
和上一篇一样,创建一个头文件,两个源文件
也是一样,在头文件SList.h中定义单链表的结构,并对类型和结构体改名
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int Type;
//定义节点结构
typedef struct SListNode
{Type data;struct SListNode* next;
}SLNode;
我们先创建几个节点,在test.c中实现
#include "SList.h" //包含头文件
void SListtest1()
{SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));//创建节点1node1->data = 1;//赋值SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));//创建节点2node2->data = 2;//赋值SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));//创建节点3node3->data = 3;//赋值SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));//创建节点4node4->data = 4;//赋值
}
int main()
{SListtest1();return 0;
}
虽然我们创建好了节点,但是这几个节点现在还不能互相找到,所以我们接下来要将这几个节点连接起来,怎么连接?存下一个节点的地址
void SListtest1()
{SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));//创建节点1node1->data = 1;//赋值SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));//创建节点2node2->data = 2;//赋值SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));//创建节点3node3->data = 3;//赋值SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));//创建节点4node4->data = 4;//赋值//将四个节点连接起来node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;
}
现在就构成了一个链表,有了这个简单的链表,我们来写一个函数打印一下里面的数据
链表的打印
在SList.h中进行函数的声明
void SLPrint(SLNode* ps);//打印
参数是链表的节点的地址
在SList.c中进行函数的实现
#include "SList.h"
void SLPrint(SLNode* ps) //打印
{SLNode* pcur = ps;//pcur指向当前链表的第一个节点
}
要打印当然就要循环遍历,写一个while循环,判断条件为pcur,不为NULL进入循环
void SLPrint(SLNode* ps) //打印
{SLNode* pcur = ps;//pcur指向当前链表的第一个节点while (pcur) //判断第一个节点是否为NULL{//....}
}
进入循环后打印内容
void SLPrint(SLNode* ps) //打印
{SLNode* pcur = ps;//pcur指向当前链表的第一个节点while (pcur){printf("%d->", pcur->data);//打印内容}
}
打印完一个数据后,要把下一个节点值部分的地址给pcur ,也就是pcur要存node2中2的地址,此时pcur不再指向node1,指向了node2
void SLPrint(SLNode* ps) //打印
{SLNode* pcur = ps;//pcur指向当前链表的第一个节点while (pcur){printf("%d->", pcur->data);//打印内容pcur = pcur->next;//指向下一个节点}printf("NULL\n");
}
在test.c中测试
测试时我们不直接传链表的第一个节点node1,而是再定义一个结构体指针plist去指向node1,让plist作为参数传过去
SLNode* plist = node1;SLPrint(plist);
虽然有点多此一举,但这样会让我们的代码更完善
运行结果
2.链表的插入
实际使用链表的时候我们不会像上面那样一个一个创建,初始时候是一个空链表,会跟顺序表类似进行空间开辟然后插入数据
2.1空间申请创建节点
插入数据前依旧是要判断空间够不够
在SList.h中进行函数的声明
SLNode* SLBuyNode(Type x);//申请空间创建节点
返回值是节点的地址,参数就是要插入的数据
在SList.c中进行函数的实现
SLNode* SLBuyNode(Type x)//申请空间创建节点
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL) //空间申请失败{perror("malloc");return 1;}newnode->data = x;newnode->next = NULL;return newnode;
}
2.2 尾插
在SList.h中进行函数的声明
void SLPushBack(SLNode** ps, Type x);//尾插
参数是链表的节点地址,是一个二级指针,和要插入的数据
我们要给函数传地址,这样形参的改变才能影响实参,对参数的理解如下,这很重要,这里提前展示将要在test.c中测试用的部分代码
在SList.c中进行函数的实现
尾插要先找到尾节点,再将尾节点和新节点连接起来 ,此时node4的地址部分就不再是NULL 而是新节点的地址
代码如何实现呢 ?先找尾节点
void SLPushBack(SLNode* ps, Type x)//尾插
{SLNode* ptail = ps;//定义尾节点,开始时指向头节点while (ptail->next != NULL)//遍历链表数据,找尾节点{ptail = ptail->next;}//跳出循环后ptail指向尾节点
}
然后我们直接调用创建节点的函数,放在找尾节点前面,最后赋值
void SLPushBack(SLNode* ps, Type x)//尾插
{SLNode* newnode = SLBuyNode(x);//申请空间创建节点SLNode* ptail = ps;//定义尾节点,开始时指向头节点while (ptail->next != NULL)//遍历链表数据,找尾节点{ptail = ptail->next;}//跳出循环后ptail指向尾节点ptail->next = newnode;//直接赋值
}
但是呢,链表在没有赋值的时候是空链表,所以我们要讨论空链表的情况,如果是空链表,直接让ps指向新节点newnode,所以最终的代码如下
void SLPushBack(SLNode** pps, Type x)//尾插
{assert(pps);//pps不可以为NULLSLNode* newnode = SLBuyNode(x);//申请空间创建节点if (*pps == NULL) //*pps可以为NULL,表示空链表{*pps = newnode;}else //非空链表{SLNode* ptail = *pps;//定义尾节点,开始时指向头节点while (ptail->next)//遍历链表数据,找尾节点{ptail = ptail->next;}//跳出循环后ptail指向尾节点ptail->next = newnode;//直接赋值}
}
我们再来看test.c中测试的代码
void SListtest2()
{SLNode* plist = NULL;//空链表SLPushBack(&plist, 1);//尾插SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLPrint(plist);//打印
}
int main()
{//SListtest1();SListtest2();return 0;
}
多插入几个数据,运行看结果,插入成功
2.3 头插
在SList.h中进行函数的声明
void SLPushHead(SLNode** pps, Type x);//头插
同样的,参数也是一个二级指针,还有要插入的数据
在SList.c中进行函数的实现
我们需要做两件事,一个是将newnode和原本的首节点连接在一起,另一件事是*pps要指向新的首节点
链表不为NULL时,代码如下
void SLPushHead(SLNode** pps, Type x)//头插
{assert(pps);//pps不能为NULLSLNode* newnode = SLBuyNode(x);//申请空间创建节点newnode->next = *pps;*pps = newnode;
}
当链表为NULL时,分析一下
当前代码也可以实现
在test.c中测试
void SListtest2()
{SLNode* plist = NULL;//空链表SLPushBack(&plist, 1);//尾插SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLPrint(plist);//打印SLPushHead(&plist, 6);//头插SLPushHead(&plist, 7);SLPushHead(&plist, 8);SLPrint(plist);//打印
}
看一下运行结果
3.链表的删除
删除数据,链表就不能为空,不然删啥呢?
3.1 尾删
在SList.h中进行函数的声明
void SLPopBack(SLNode** pps);//尾删
在SList.c中进行函数的实现
很显然,首先就是找尾节点,找到尾节点之后直接释放吗?node4释放后node3->next里面依然存放着node4的地址,但此时指向的空间已经没了,此时的指针就成了一个野指针 ,所以我们还要将被释放节点的前一个节点的指针置空
所以我们还要找尾节点的前一个节点
void SLPopBack(SLNode** pps)//尾删
{assert(pps && *pps);//pps和链表都不能为空SLNode* prev = *pps;//定义尾节点前一个节点SLNode* ptail = *pps;//定义尾节点while (ptail->next){prev = ptail;ptail = ptail->next;}//此时找到了尾节点和尾节点前一个节点free(ptail);//释放尾节点ptail = NULL;//置空prev->next = NULL;//置空尾节点前一个节点
}
如果这个链表只有一个节点呢,这个代码可行吗?来分析一下
我们把节点释放并置空后,prev->next就不存在了,函数最后一句代码就不能实现,所以,当链表里只有一个节点时,直接释放就行了
void SLPopBack(SLNode** pps)//尾删
{assert(pps && *pps);//pps和链表都不能为空if ((*pps)->next == NULL)//链表只有一个节点{free(*pps);//释放节点*pps = NULL;//置空}else //链表不止一个节点{SLNode* prev = *pps;//定义尾节点前一个节点SLNode* ptail = *pps;//定义尾节点while (ptail->next){prev = ptail;ptail = ptail->next;}//此时找到了尾节点和尾节点前一个节点free(ptail);//释放尾节点ptail = NULL;//置空prev->next = NULL;//置空尾节点前一个节点}
}
在test.c中测试
void SListtest2()
{SLNode* plist = NULL;//空链表SLPushBack(&plist, 1);//尾插SLPushBack(&plist, 2);SLPrint(plist);//打印SLPushHead(&plist, 6);//头插SLPushHead(&plist, 7);SLPrint(plist);//打印SLPopBack(&plist);//尾删SLPrint(plist);//打印
}
看下结果,尾删成功
3.2 头删
在SList.h中进行函数的声明
void SLPopHead(SLNode** pps);//头删
在SList.c中进行函数的实现
我们要删除头节点,跟删除尾节点不一样,如果我们把首节点释放掉,还能找到首节点里的next吗?不能,不能的话就找不到第二个节点以及剩下的节点
我们应该先把下一个节点的信息存储起来
SLNode* Next = (*pps)->next;//存节点
然后把原头节点释放
free(*pps);
最后让*pps指向新的头节点
*pps = Next;
所以代码如下
void SLPopHead(SLNode** pps)//头删
{assert(pps && *pps);//pps和链表都不能为空SLNode* Next = (*pps)->next;//存节点free(*pps);*pps = Next;
}
当链表只有一个节点时,分析一下,上面的代码也是可以完成的
我们在test.c中测试一下
void SListtest2()
{SLNode* plist = NULL;//空链表SLPushBack(&plist, 1);//尾插SLPushBack(&plist, 2);SLPrint(plist);//打印SLPushHead(&plist, 6);//头插SLPushHead(&plist, 7);SLPrint(plist);//打印SLPopBack(&plist);//尾删SLPrint(plist);//打印SLPopHead(&plist);//头删SLPrint(plist);//打印
}
下篇我们再继续介绍更多内容,拜拜~
相关文章:

【数据结构】单链表(一)
上一篇【数据结构】顺序表-CSDN博客 我们了解了顺序表,但是呢顺序表涉及到了一些问题,比如,中间/头部的插入/删除,时间复杂度为O(N);增容申请空间、拷贝、释放旧空间会有不小的消耗;增容所浪费的空间... 我们如何去解…...

SCI一区 | Matlab实现INFO-TCN-BiGRU-Attention向量加权算法优化时间卷积双向门控循环单元注意力机制多变量时间序列预测
SCI一区 | Matlab实现INFO-TCN-BiGRU-Attention向量加权算法优化时间卷积双向门控循环单元注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现INFO-TCN-BiGRU-Attention向量加权算法优化时间卷积双向门控循环单元注意力机制多变量时间序列预测预测效果基本介绍模型描述程…...

Coursera吴恩达《深度学习》课程总结(全)
这里有Coursera吴恩达《深度学习》课程的完整学习笔记,一共5门课:《神经网络和深度学习》、《改善深层神经网络》、《结构化机器学习项目》、《卷积神经网络》和《序列模型》, 第一门课:神经网络和深度学习基础,介绍一…...

C# 操作PDF表单 - 创建、填写、删除PDF表单域
通常情况下,PDF文件是不可编辑的,但PDF表单提供了一些可编辑区域,允许用户填写和提交信息。PDF表单通常用于收集信息、反馈或进行在线申请,是许多行业中数据收集和交换的重要工具。 PDF表单可以包含各种类型的输入控件࿰…...

Astropy:探索宇宙奥秘的Python工具箱
Astropy:探索宇宙奥秘的Python工具箱 什么是Astropy库? Astropy 是一个面向天文学领域的 Python 库,旨在提供常用的天文学数据结构、单位转换、物理常数以及天文学计算方法等功能。它为天文学家和研究人员提供了丰富的工具,用于处理和分析天文…...

java数据结构与算法刷题-----LeetCode684. 冗余连接
java数据结构与算法刷题目录(剑指Offer、LeetCode、ACM)-----主目录-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/123063846 文章目录 并查集 并查集 解题思路:时间复杂度O( n ∗ l o g 2…...

循环神经网络简介
卷积神经网络相当于人类的视觉,但是它并没有记忆能力,所以它只能处理一种特定的视觉任务,没办法根据以前的记忆来处理新的任务。比如,在一场电影中推断下一个时间点的场景,这个时候仅依赖于现在的场景还不够࿰…...
计算机网络 子网掩码与划分子网
一、实验要求与内容 1、需拓扑图和两个主机的IP配置截图。 2、设置网络A内的主机IP地址为“192.168.班内学号.2”,子网掩码为“255.255.255.128”,网关为“192.168.班内学号.1”;设置网络B内的主机IP地址为“192.168.班内学号100.2”&#…...

HUD抬头显示器中如何设计LCD的阳光倒灌实验
关键词:阳光倒灌实验、HUD光照温升测试、LCD光照温升测试、太阳光模拟器 HUD(Head-Up Display,即抬头显示器)是一种将信息直接投影到驾驶员视线中的技术,通常用于飞机、汽车等驾驶舱内。HUD系统中的LCD(Liq…...

Shoplazza闪耀Shoptalk 2024,新零售创新解决方案引领行业新篇章!
在近期举办的全球零售业瞩目盛事——Shoptalk 2024大会上,全球*的零售技术平台-店匠科技(Shoplazza)以其*的创新实力与前瞻的技术理念,成功吸引了与会者的广泛关注。此次盛会于3月17日至20日在拉斯维加斯曼德勒湾隆重举行,汇聚了逾万名行业精英。在这场零售业的盛大聚会上,Shop…...
Linux:sprintf、snprintf、vsprintf、asprintf、vasprintf比较
这些函数都在stdio.h里,不过不同系统不同库,有些函数不一定提供。 1. sprintf 函数原型: int sprintf (char *str, const char *format, ...); extern int sprintf (char *__restrict __s, const char *__restrict __format, ...); 功能是将…...
Github远程仓库改名字之后,本地git如何配置?
文章目录 缘由解决方案 缘由 今天在github创建一个仓库,备份一下本地电脑上的资料。起初随便起一个仓库名字,后来修改之。既然远程仓库改名,那么本地仓库需要更新地址。这里采用SSH格式。 解决方案 如果你的GitHub仓库改名了,你…...
Objective-C学习笔记(ARC,分类,延展)4.10
1.自动释放池autoreleasepool:存入到自动释放池的对象,在自动释放池销毁时,会自动调用池内所有对象的release方法。调用autorelease方法将对象放入自动释放池。 Person *p1 [ [ [ Person alloc ] init ] autorelease]; 2.在类方法里写一个…...

02 Git 之IDEA 集成使用 GitHub(Git同时管理本地仓库和远程仓库)
2 .IDEA 集成使用 GitHub(Git同时管理本地仓库和远程仓库) 首先在 IDEA 的设置中绑定 GitHub 的账号 先创建一个 test1.txt 文件,内容为 aaa. 最上一栏 VCS, SHARE ON GitHub,然后选择要发送到远程仓库的文件即可。…...

CSS滚动条样式修改
前言 目前我们可以通过 CSS伪类 来实现滚动条的样式修改,以下为修改滚动条样式用到的CSS伪类: ::-webkit-scrollbar — 整个滚动条 ::-webkit-scrollbar-button — 滚动条上的按钮 (上下箭头) ::-webkit-scrollbar-thumb — 滚动条上的滚动滑块 ::-web…...

《零秒思考》像麦肯锡精英一样思考 - 三余书屋 3ysw.net
零秒思考:像麦肯锡精英一样思考 大家好,今天我们要深入探讨的著作是《零秒思考》。在领导提出问题时,我们常常会陷入沉思,却依然难以有所进展,仿佛原地踏步,但是身边的同事却能够立即给出清晰的回答。这种…...

使用docker制作Android镜像(实操可用)
一、安装包准备 1、准备jdk 下载地址:Java Downloads | Oracle 注意版本!!!!!! 我下载的jdk17,不然后面构建镜像报错,就是版本不对 2、准备安装的工具包 ttps://dev…...

大厂MVP技术JAVA架构师培养
课程介绍 这是一个很强悍的架构师涨薪计划课程,课程由专家级MVP讲师进行教学,分为是一个章节进行分解式面试及讲解,不仅仅是面试,更像是一个专业的架构师研讨会课程。课程内容从数据结构与算法、Spring Framwork、JVM原理、 JUC并…...
uniapp实现文件和图片选择上传功能实现
主要介绍了uni-file-picker文件选择上传,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下 上传一张: <template><view class="container example"><uni-forms ref="baseForm" …...

2024认证杯数学建模C题思路模型代码
目录 2024认证杯数学建模C题思路模型代码:4.11开赛后第一时间更新,获取见文末名片 以下为2023年认证杯C题: 2024年认证杯数学建模C题思路模型代码见此 2024认证杯数学建模C题思路模型代码:4.11开赛后第一时间更新,获…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...