【数据结构】单链表之--无头单向非循环链表

前言:前面我们学习了动态顺序表并且模拟了它的实现,今天我们来进一步学习,来学习单链表!一起加油各位,后面的路只会越来越难走需要我们一步一个脚印!

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:数据结构 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


单链表

今天我们要实现的全部功能就如下所示,功能很多我们一步一步来,一起来手撕链表吧!加油!

typedef int SLNDataType;typedef struct SList
{int val;struct SList* next;
}SLNode;//单链表的打印
void SLTPrint(SLNode* phead);//单链表的尾插
void SLTPushBack(SLNode** pphead, SLNDataType x);//单链表的头插
void SLTPushFront(SLNode** pphead, SLNDataType x);//单链表的尾删
void SLTPopback(SLNode** pphead);//单链表的头删
void SLTPopFront(SLNode** pphead);//单链表的元素查找
SLNode* SLFind(SLNode* phead, SLNDataType x);//单链表的插入-在pos的前面插入
SLNode* SLInsert(SLNode** pphead,SLNode* pos, SLNDataType x);//需要自己思考一级还是二级//单链表的删除
void SLTErase(SLNode** pphead, SLNode* pos);//单链表的销毁
void SLTDestroy(SLNode** pphead);//单链表的删除-pos之后的元素
void SLTEraseAfter(SLNode* pos,SLNDataType x);//单链表插入-pos之后插入
void SLTInsertAfter(SLNode* pos,SLNDataType x);

动态申请一个结点

代码思路,首先我们要开辟一个结构体,来开始我们今天的单链表

typedef struct SList
{int val;struct SList* next;
}SLNode;

当然了,我们肯定得写一个接口,来申请动态开辟的一个结点(这个我们在前面写顺序表的时候就写过了,就不过多介绍这个了),可以看下图帮助自己理解在这里插入图片描述

SLNode* CreateNewNode(SLNDataType x)//开辟一个新的节点
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL)//判断是否有空间{perror("malloc fail");exit(-1);}newnode->val = x;newnode->next = NULL;return newnode;
}

单链表的打印

代码思路:单链表中我们可以知道它是如下图这种形式,每一个结构体中存着下个节点的地址,我们可以通过判断结构体指针是否为空指针来依次打印
在这里插入图片描述

void SLTPrint(SLNode* phead)
{SLNode* cur = phead;//通过头节点依次访问while (cur != NULL){printf("%d->", cur->val);cur = cur->next;}printf("NULL\n");
}

单链表的尾插

在写代码之前,我们需要重新复习一下,对形参的修改不会改变实参,形参是实参的一个临时拷贝(请牢牢记住,后面有很大的作用),这在后面帮助我们理解单链表有很大的帮助。
我们先来看一串代码

void swap(int* a, int* b)
{int tmp = 0;tmp = *a;*a = *b;*b = tmp;
}int main()
{int a = 3;int b = 5;swap(&a, &b);printf("a = %d,b = %d", a, b);return 0;
}

我们要想改变
在这里插入图片描述


void swap(int** a, int** b)
{int tmp = 0;tmp = **a;**a = **b;**b = tmp;
}int main()
{int arr1[] = { 1 };int arr2[] = { 2 };int* a = arr1;int* b = arr2;swap(&a, &b); printf("*a = %d, *b = %d",*a, *b);return 0;
}

在这里插入图片描述
由这些可以知道,我们要想修改一级指针里面的值,我们要用二级指针接收。接下来我们就开始上我们的第一盘凉菜了!
代码思路:首先我们肯定要考虑两种情况,即一种是链表是空的什么都没有,另一种即链表中有值,需要我们尾增新的值,我们可以借助下图来帮助我们分析!我们通过循环找到该链表的尾结点,然后让尾部结点中的next,假如链表中没有值是空链表,我们直接指向新的结点即可。
在这里插入图片描述

void SLTPushBack(SLNode** pphead, SLNDataType x)//尾插节点
{assert(pphead);//判断传过来的链表是否存在SLNode* newnode = CreateNewNode(x);//开辟节点if (*pphead == NULL)//判断传来的是否是空指针,如果为空就直接开辟新的节点{*pphead = newnode;}else{SLNode* tail = *pphead;while (tail->next != NULL)//只有尾结点的next才是空{tail = tail->next;//找到尾节点}tail->next = newnode;//将为节点的值指向新节点}
}

这里大家肯定会有很多疑问,为什么是 **phead ,我们来看下图
在这里插入图片描述


函数测试与结果运行图:

void Test1()
{SLNode* plist = NULL;SLTPushBack(&plist, 1);//尾插SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);
}int main()
{Test1();return 0;
}

在这里插入图片描述


单链表的头插

代码思路:单链表的头插我们依然得借助图像来帮助我们分析如下图,我们让*phead指向newnode开辟的结点,在让newnode->next指向原本的头结点即可完成头插,当然我们依然得用二级指针接收,因为我们要修改的一级指针。
在这里插入图片描述
代码实现:

void SLTPushFront(SLNode** pphead, SLNDataType x)//单链表的头插
{assert(pphead);SLNode* newnode = CreateNewNode(x);//开辟一个新的节点newnode->next = *pphead;//将头节点地址存放在next中*pphead = newnode;  //在让头节点指向Newnode,此时newnode就称为了头节点
}

函数测试与效果图:

void Test2()
{SLNode* plist = NULL;SLTPushFront(&plist, 2);//头插SLTPushFront(&plist, 2);SLTPushFront(&plist, 9);SLTPrint(plist);
}int main()
{//Test1();Test2();return 0;
}

在这里插入图片描述


单链表的尾删

思路分析:在单链表的尾部删除中,我们需要考虑两种情况一种为单节点,一种为多节点,即一种删除完后链表中的值为空,另一种即删除最后一个后仍然还有结点。当然了,我们依然得先画图分析,如下图。我们看图一,可以知道,我们直接找到 *phead这个结点将他free掉即可,然后将 *phead置为空指针,即可完成单结点的删除。我们看图二,我们得找到一个尾结点将它释放,将尾部结点的前一个结点中的next即保留最后一个结点中的地址,让它置为空指针即删除完毕,由此我们可以通过一个快慢指针,一个指针往后走,一个保留前一个结点的地址,因此我们可以找到最后一个结点并保留前一个结点的地址。
在这里插入图片描述


在这里插入图片描述


代码思路:

void SLTPopback(SLNode** pphead)//单链表的尾删
{//一个节点//多个节点assert(pphead);assert(*pphead);if ((*pphead)->next == NULL)//单节点{free(*pphead);//释放pphead所在的空间*pphead = NULL;//将pphead置为空指针}else//多节点{SLNode* tail = *pphead;//铜鼓快慢指针来找到节点SLNode* prev = NULL;while (tail->next != NULL)//找到尾节点{//倒数第一步(尾节点的前一个节点时)prev = tail;//此时prev就是记录后一个节点的地址tail = tail->next;//此时找到了NULL}free(tail);//释放掉记录尾结点的地址prev->next = NULL;//将此时赋值为NULL即删除成功}
}

函数测试与运行结果:

void Test3()
{SLNode* plist = NULL;printf("打印删除之前的\n");SLTPushFront(&plist, 2);//头增SLTPushFront(&plist, 2);SLTPushFront(&plist, 9);SLTPrint(plist);printf("打印删除之后的\n");SLTPopback(&plist);//尾删SLTPopback(&plist);SLTPrint(plist);
}int main()
{//Test1();//Test2();Test3();return 0;
}

在这里插入图片描述


单链表的头删

思路分析:实现头删,我们就是把头部中的空间free掉,在将 *phead指向原本的第二个节点即可,因此我们需要用一个结构体指针指向第二个节点将其保留下来传给原本的头指针。可以通过下图帮助自己分析,如下图。
在这里插入图片描述
代码思路实现:

void SLTPopFront(SLNode** pphead)//头删
{//空assert(pphead);//多个节点SLNode* tmp = (*pphead)->next;free(*pphead);*pphead = tmp;	
}

测试函数与运行结果:

void Test4()
{SLNode* plist = NULL;printf("打印删除之前的\n");SLTPushFront(&plist, 2);//尾增SLTPushFront(&plist, 2);SLTPushFront(&plist, 9);SLTPrint(plist);printf("打印删除之后的\n");SLTPopFront(&plist);//头删SLTPopFront(&plist);SLTPrint(plist);
}
int main()
{//Test1();//Test2();//Test3();Test4();return 0;
}

在这里插入图片描述


单链表的数值查找

思路分析:我们可以通过指针去一次遍历链表中的数据,找到对应值即找到了,返回此时指针的地址,遍历到最后一个NULL也没有找到时,即返回空指针,如下图在这里插入图片描述
代码实现:

SLNode* SLFind(SLNode* phead, SLNDataType x)//查找
{assert(phead);SLNode* tail = phead;while (tail){if(tail->val == x){return tail;//返回此时指针的值}tail = tail->next;}return NULL;
}

函数测试与效果图:

void Test5()
{SLNode* plist = NULL;printf("打印删除之前的\n");SLTPushFront(&plist, 2);//尾增SLTPushFront(&plist, 2);SLTPushFront(&plist, 9);SLTPrint(plist);printf("查找结果:\n");printf("%p\n",SLFind(plist, 2));//查找数printf("%p\n", SLFind(plist, 99));
}
int main()
{//Test1();//Test2();//Test3();//Test4();Test5();return 0;
}

在这里插入图片描述


单链表的插入- 在pos的前面插入

思路分析:如果是多节点要想实现在pos的前面插入,首先我们要找到pos的前面一个节点让它指向我们新开辟的节点newnode然后再让newnode->next指向我们原本pos的所在的节点就完成了头插,如下图1所示。如果是单节点的时候,就是相当于头插,我们只需要判断是否是单节点,如果是就直接调用头插函数即可。
在这里插入图片描述
代码实现:

SLNode* SLInsert(SLNode** pphead, SLNode* pos, SLNDataType x)//单链表插入
{assert(pphead);assert(pos);assert(*pphead);//单节点if (*pphead == pos){//头插SLTPushFront(pphead, x);}else{//多节点SLNode* tail = *pphead;SLNode* newnode = CreateNewNode(x);while (tail->next != pos){tail = tail->next;}tail->next = newnode;newnode->next = pos;}
}

函数测试与效果图:

void Test6()
{SLNode* plist = NULL;printf("原本的值\n");SLTPushFront(&plist, 2);//尾增SLTPushFront(&plist, 2);SLTPushFront(&plist, 9);SLTPrint(plist);printf("插入之后的结果\n");SLNode* address = SLFind(plist, 2);//查找数SLInsert(&plist, address, 10);SLTPrint(plist);
}
int main()
{//Test1();//Test2();//Test3();//Test4();//Test5();Test6();return 0;
}

在这里插入图片描述


单链表数值的删除-删除Pos前的值

思路分析:当然了单链表的删除我们依然得采用俩种情况,一种情况为单节点,一种情况为多节点,我们先来分析多节点,如果是多节点的情况,我们应当找到pos的节点将它释放掉,并且我们应当将pos的前一个节点将他记录下来,并让它指向pos之后的一个节点,此时我们即可完成数值的删除。如下图所示,如果是单节点的情况,我们可以直接当成头删,直接调用头删函数即可。
在这里插入图片描述

代码实例:

void SLTErase(SLNode** pphead, SLNode* pos)//数值的删除
{assert(pphead);//判断传来的结构体是否存在assert(*pphead);//判断是否为空指针assert(pos);//判断空地址if (*pphead == pos){SLTPopFront(pphead);//头删}else{SLNode* tail = *pphead;while (tail->next != pos){tail = tail->next;}tail->next = pos->next;free(pos);pos = NULL;}
}

函数测试与效果图:

void Test7()
{SLNode* plist = NULL;printf("原本的值\n");SLTPushFront(&plist, 2);//尾增SLTPushFront(&plist, 3);SLTPushFront(&plist, 2);SLTPushFront(&plist, 9);SLTPrint(plist);printf("删除之后的结果\n");SLNode* address = SLFind(plist, 2);//查找数SLTErase(&plist, address);SLTPrint(plist);
}
int main()
{//Test1();//Test2();//Test3();//Test4();//Test5();//Test6();Test7();return 0;
}

在这里插入图片描述


单链表的销毁

思路分析:要想销毁单链表中的所有值,我们只需要把单链表中的每个节点给它释放,并最后让头节点指向空指针即可,因此我们需要借助两个指针,一个指针指向tail的下一个节点,当释放掉tail后让tail可以指向下一个节点再依次释放,这样就可以达到链表的销毁的作用,如下图所示。
在这里插入图片描述
代码实例:

void SLTDestroy(SLNode** pphead)//单链表的销毁
{assert(*pphead);//判断传来的是否已经是空指针SLNode* tail = *pphead;SLNode* pre = NULL;while (tail != NULL)//找到尾节点{pre = tail->next;free(tail);//依次释放tail = pre;}*pphead = NULL;
}

函数测试与效果图:

void Test8()
{SLNode* plist = NULL;printf("原本的值\n");SLTPushFront(&plist, 2);//尾增SLTPushFront(&plist, 3);SLTPushFront(&plist, 2);SLTPushFront(&plist, 9);SLTPrint(plist);printf("删除之后的结果\n");SLTDestroy(&plist);SLTPrint(plist);
}int main()
{//Test1();//Test2();//Test3();//Test4();//Test5();//Test6();Test8();return 0;
}

在这里插入图片描述


结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。


🫵🫵🫵 祝各位接下来好运连连 💞

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

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

相关文章

跨境电商年底风控升级,测评养号如何选择稳定且纯净的IP环境?

随着年底跨境电商平台风控的升级,许多测评团队的账号存活率有所下降。对于自养号测评的卖家来说,IP的重要性不言而喻。除了设置参数阻断,IP的质量也直接影响到账户的稳定性和成功率。因此,在年底这个特殊时期,所有测评…

【c++入门】引用详解 | auto的类型推导 | 范围for循环 | nullptr空指针

🎥 屿小夏 : 个人主页 🔥个人专栏 : C入门到进阶 🌄 莫道桑榆晚,为霞尚满天! 文章目录 📑前言🌤️引用☁️引用的概念☁️引用的特性⭐引用在定义时必须初始化 ☁️常引用…

分享Python7个爬虫小案例(附源码)

本次的7个python爬虫小案例涉及到了re正则、xpath、beautiful soup、selenium等知识点,非常适合刚入门python爬虫的小伙伴参考学习。注:若涉及到版权或隐私问题,请及时联系我删除即可。 1.使用正则表达式和文件操作爬取并保存“某吧”某帖子…

NSSCTF web刷题记录4

文章目录 [NSSRound#4 SWPU]1zweb(revenge)[强网杯 2019]高明的黑客[BJDCTF 2020]Cookie is so subtle![MoeCTF 2021]fake game[第五空间 2021]PNG图片转换器[ASIS 2019]Unicorn shop[justCTF 2020]gofs[UUCTF 2022 新生赛]phonecode[b01lers 2020]Life On Mars[HZNUCTF 2023 f…

赴日工作赴日IT 如何找到一份日本IT工作?

IT在日本属于普通白领工作,那些想靠IT工作发财就不必考虑了。但是靠IT工作能安安稳稳的过个自己的小日子没问题,买房买车问题不大,作为一个普通人,在日本可以过的比较舒服。对有在日本长期发展的打算的还算是一个比较好的方向&…

利用maven的dependency插件分析工程的依赖

dependency:analyze https://maven.apache.org/plugins/maven-dependency-plugin/analyze-mojo.html 分析项目的依赖,确定哪些:用了并且声明了、用了但没有声明、没有使用但声明了。 dependency:analyze可以单独使用,所以它总是会执行test-…

【uniapp】解决在H5谷歌浏览器下 u-input 标签 设置只读后,click事件不生效

【问题描述】 谷歌浏览器更新后,h5模式下原本的input外层view中的click事件不触发了?? 但是更换浏览器后就可以,打包app也是正常可以触发的,本来是没打算兼容h5,既然遇到了就记录一下~ 【解决办法】 使u–input里写上readonly&…

arcgis 批量删除Table中的某些Field

当shp或者table文件较少时,可以手动删除每个文件中的某些字段,当文件较多时,就需要使用arcpy或者model进行处理。

动态IP和静态IP哪个安全,该怎么选择

随着互联网的普及,越来越多的人开始关注网络安全问题。其中,IP地址作为网络通信中的重要组成部分,也成为了人们关注的焦点。 在IP地址中,动态IP和静态IP是两种不同的分配方式,它们各自具有不同的特点,那么…

《golang设计模式》第三部分·行为型模式-04-迭代器模式(Iterator)

文章目录 1. 概念1.1 角色1.2 类图 2. 代码示例2.1 需求2.2 代码2.3 类图 1. 概念 迭代器(Iterator)能够在不暴露聚合体内部表示的情况下,向客户端提供遍历聚合元素的方法。 1.1 角色 InterfaceAggregate(抽象聚合)…

深入理解强化学习——多臂赌博机:基于置信度上界的动作选择

分类目录:《深入理解强化学习》总目录 因为对动作—价值的估计总会存在不确定性,所以试探是必须的。贪心动作虽然在当前时刻看起来最好,但实际上其他一些动作可能从长远看更好。 ϵ − \epsilon- ϵ−贪心算法会尝试选择非贪心的动作&#xf…

安装RabbitMQ

安装RabbitMQ 下载需要的两个包 # 这直接就可以安装了,下面 ‘上传对应的rmp包’ 操作 [rootrabbitmq-1 ~]# curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash [rootrabbitmq-1 ~]# yum install erlang-21.3.8.2…

竞赛 深度学习驾驶行为状态检测系统(疲劳 抽烟 喝水 玩手机) - opencv python

文章目录 1 前言1 课题背景2 相关技术2.1 Dlib人脸识别库2.2 疲劳检测算法2.3 YOLOV5算法 3 效果展示3.1 眨眼3.2 打哈欠3.3 使用手机检测3.4 抽烟检测3.5 喝水检测 4 最后 1 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于深度学习的驾…

6.判断是不是闰年

#include<stdio.h>void fun(int year){if(year%40&&year%100!0||year%4000)printf("%d 是闰年\n",year);elseprintf("%d 不是闰年\n",year);}int main(){int year;scanf("%d",&year);fun(year);return 0;}

Sentinel 哨兵数据 更新下载地址 2023年11月

1. 欧空局官方下载 2023年11月开始&#xff0c;原来欧空局的下载地址和应用有了变化&#xff0c;现在迁移到以下新地址下载&#xff1a; https://dataspace.copernicus.eu/ 我这边测试需要重新注册用户才能进行登录和使用&#xff0c;界面使用和之前差不多&#xff0c;具体操作…

python单元测试框架(继承、unittest参数化、断言、测试报告)

一、继承 继承能解决什么问题&#xff1f; unittest每个模块都要用到前提条件以及清理&#xff0c;如果有上百个模块&#xff0c;我们要改域名和浏览器&#xff0c;就会工作量很大特别麻烦&#xff0c;这时我们可以用继承的思想只用改一次 我们可以将前提和清理提出来单独放…

ubuntu20.04 安装cudnn

中文地址是.cn&#xff1a;cuDNN 历史版本 | NVIDIA 开发者 英文地址是.com&#xff1a;cuDNN 历史版本 | NVIDIA 开发者 1、下载cudnn&#xff1a;cudnn-local-repo-ubuntu2004-8.8.1.3_1.0-1_amd64.deb 解压并安装&#xff1a;sudo dpkg -i cudnn-local-repo-ubuntu2004-8.8…

pytorch与cudatoolkit,cudnn对应关系及安装相应的版本

文章目录 一.cuda安装二、nvidia 驱动和cuda runtime 版本对应关系三、安装cudatoolkit,cudnn对应版本四、cuda11.2版本的对应安装的pytorch版本及安装五、相关参考 一.cuda安装 1.确定当前平台cuda可以安装的版本 安装好显卡驱动后&#xff0c;使用nvidia-smi命令可以查看这个…

P1903 [国家集训队] 数颜色 / 维护队列

带修改的莫队 带修改的莫队就是在基础莫队的基础上增加了一维属性&#xff0c;之前只需要维护l&#xff0c;r现在还需要维护一下时间t&#xff0c;排序还是先按照左端点块儿号排序&#xff0c;然后右端点块儿号排序&#xff0c;最后按时间排序。其它的都是差不多的。 #include…

计算机基础知识44

overflow溢出属性 visible&#xff1a;默认值&#xff0c;内容不会被修剪&#xff0c;会呈现在元素框之外。hidden&#xff1a;内容会被修剪&#xff0c;并且其余内容是不可见的。scroll&#xff1a;内容会被修剪&#xff0c;但是浏览器会显示滚动条以便查看其余的内容。auto: …