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

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

💖 博主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.mzph.cn/news/137596.shtml

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

相关文章

Vue中模板语法与el 和 data 的两种写法

模板语法 标签语法 //在.html文件中 <body><h3>{{name}}</h3> </body> <script>new Vue({el:#root,data:{name:jack,url:网页地址}}) </script> 指令语法 标签语法和指令语法的简单区分&#xff0c;就是指令语法用v-on,v-bind,v-model指…

ubuntu 16.04.5 安装 vivado 2019.1 完整编译AD9361的环境

一、前期安装 1、安装ncurses库&#xff08;已经包含了&#xff0c;其他的os需要安装&#xff09; sudo apt install libncurses5二、安装 sudo ./xsetup使用lic进行激活。 三、安装后 输入指令 sudo gedit ~/.bashrc 末尾添加 source /opt/Xilinx/Vivado/2019.1/setti…

python 在 __init__.py文件中修改某变量的值(非手动)

python的__init__.py文件在Python包被导入或使用时开始执行。 当一个包被导入时&#xff0c;Python会自动执行该包下的__init__.py文件。这意味着__init__.py文件中的代码会在导入包时立即执行。 如果我们的应用程序需要在引入包的时候&#xff0c;对__init__.py文件的变量设…

引用(类名后加符号)和指针的区别

它们的区别&#xff1a; ①从现象上看&#xff0c; 指针在运行时可以改变其所指向的值&#xff0c; 而引用一旦和某个对象绑定后就不再改变。 这句话可以理解为&#xff1a; 指针可以被重新赋值以指向另一个不同的对象。 但是引用则总是指向在初始化时被指定的对象&#x…

无人机航拍技术基础入门,无人机拍摄的方法与技巧

一、教程描述 买了无人机&#xff0c;可是我不敢飞怎么办&#xff1f;禁飞区越来越多&#xff0c;到底哪儿才能飞&#xff1f;我的无人机跟你一样&#xff0c;为什么我拍不出大片&#xff1f;厂家的说明书看不进去&#xff0c;有没有一套无人机的课程&#xff0c;可以快速上手…

ruoyi前后端分离版本开发框架解读---让你快速入门

后端结构 com.ruoyi ├── common // 工具类 │ └── annotation // 自定义注解 │ └── config // 全局配置 │ └── constant // 通用常量 │ └── core …

sqli-labs-1

文章目录 Less-01Less-02Less-03Less-04 Less-01 1.输入不同的id值&#xff0c;可以获取不同的用户信息&#xff1a; 2.在sql后拼接’or11–&#xff0c;并没有获取到所有用户的信息&#xff0c;猜测可能用了limit语句 3.构造错误的sql语句&#xff0c;果然有limit报错: …

《网络协议》03. 传输层(TCP UDP)

title: 《网络协议》03. 传输层&#xff08;TCP & UDP&#xff09; date: 2022-09-04 22:37:11 updated: 2023-11-08 15:58:52 categories: 学习记录&#xff1a;网络协议 excerpt: 传输层、UDP、TCP&#xff08;可靠传输&#xff0c;流量控制&#xff0c;拥塞控制&#xf…

Go采集代理框架

代理服务器在网络爬虫、数据采集和反爬虫等场景中起着重要的作用。通过使用代理服务器&#xff0c;我们可以隐藏客户端的真实IP地址并提高访问速度。Go语言作为一种强大且可靠的编程语言&#xff0c;提供了很多库和工具来实现代理采集框架。在本文中&#xff0c;我们将介绍如何…

汽车标定技术(八)--MPC57xx是如何支持标定的页切换

目录 1.页切换的概念 1.1 标定常量的理解 1.2 页切换 2.MPC57xx的Overlay模块 3.小结 1.页切换的概念 在汽车标定测量中&#xff0c;有一个概念我想很多人都听过&#xff0c;但是实际上在项目里没有用到过&#xff0c;那就是今天要讲的页切换概念。在讲页切换的时候&#…

手机怎么打包?三个方法随心选!

有的时候&#xff0c;电脑不在身边&#xff0c;只有随身携带的手机&#xff0c;这个时候又急需把文件打包发送给同事或者同学&#xff0c;如何利用手机操作呢&#xff1f;下面介绍了具体的操作步骤。 一、通过手机文件管理自带压缩功能打包 1、如果是iOS系统&#xff0c;就在手…

angular+ionic+npm项目运行

angularionicnpm项目运行 错误记录&#xff08;1&#xff09;Downloading binary from https://github.com/sass/node-sass/releases/download/v4.14.1/win32-x64-72_binding.node&#xff08;2&#xff09;The npm warning "A requires a peer of B but none is installe…

【PHP函数封装】分分钟帮你实现数据脱敏处理, 支持手机号码、邮箱、身份证号 中文字符串!

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&#x1…

MIPSsim模拟器 使用说明

&#xff08;一&#xff09; 启动模拟器 双击MIPSsim.exe&#xff0c;即可启动该模拟器。模拟器启动时&#xff0c;自动将自己初始化为默认状态。所设置的默认值为&#xff1a; u所有通用寄存器和浮点寄存器为全0&#xff1b; u内存清零&#xff1b; u流水寄存器为全0&#xff…

【ElasticSearch系列-06】Es集群架构的搭建以及集群的核心概念

ElasticSearch系列整体栏目 内容链接地址【一】ElasticSearch下载和安装https://zhenghuisheng.blog.csdn.net/article/details/129260827【二】ElasticSearch概念和基本操作https://blog.csdn.net/zhenghuishengq/article/details/134121631【三】ElasticSearch的高级查询Quer…

P1547 [USACO05MAR] Out of Hay S 题解

文章目录 题目描述输入格式输出格式样例样例输入样例输出 完整代码 题目描述 Bessie 计划调查 N N N&#xff08; 2 ≤ N ≤ 2 000 2 \leq N \leq 2\,000 2≤N≤2000&#xff09;个农场的干草情况&#xff0c;它从 1 1 1 号农场出发。农场之间总共有 M M M&#xff08; 1 ≤…

深入理解ClickHouse跳数索引

一、跳数索引​ 影响ClickHouse查询性能的因素很多。在大多数场景中&#xff0c;关键因素是ClickHouse在计算查询WHERE子句条件时是否可以使用主键。因此&#xff0c;选择适用于最常见查询模式的主键对于表的设计至关重要。 然而&#xff0c;无论如何仔细地调优主键&#xff…

14:00面试,14:06就出来了,问的问题有点变态。。。。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到5月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

跨境电商:自养买家账号测评,你需要了解的细节

在跨境电商的浪潮中&#xff0c;自养买家账号测评已经成为了一种趋势。近期&#xff0c;不少跨境卖家咨询自养买家账号测评的相关问题&#xff0c;看来有必要再讲解一下卖家测评的一些细节。今天将着重介绍自养号测评的相关内容。 首先&#xff0c;什么叫做自养号测评呢&#x…

pytest + yaml 框架 -58.运行报告总结summary.json

前言 用例运行结束后&#xff0c;在本地生成summary.json 文件&#xff0c;总结运行结果。 v1.5.1版本更新内容&#xff1a; 1.解决参数化&#xff0c;中文在控制台输出问题 2.保存用例结果summary.json 保存用例结果summary.json 命令行执行用例 pytest运行结束&#xff0…