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

news2024/11/15 23:38:52

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

💖 博主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.coloradmin.cn/o/1181532.html

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

相关文章

kafka可视化工具

Offset Explorer kafka可视化工具

Spring Boot 统一处理功能

目录 1.用户登陆权限验证 1.1 每个方法验证 1.2 Spring AOP 用户统一登陆验证 1.3 拦截器 1.3.1 自定义拦截器 1.3.2 将自定义拦截器配置到系统设置中,并且设置拦截规则 1.3.3 排除所有的静态资源 1.4 登录拦截器(练习) 1.5 拦截器原…

二叉树—相关计算题

目录 一、概念题 二、计算题 1、节点数 2、深度 3、遍历序列 一、概念题 1、在用树表示的目录结构中,从根目录到任何数据文件,有( )通道 答案:唯一一条,树的特点是不相交,所以不可能有多…

CAN总线数据采集工具PCAN的使用教程

系列文章目录 文章目录 系列文章目录pcan使用PCAN-Explorer 5安装PCAN-USB Pro安装如下PEAK-System_Driver-Setup安转如下PCAN-View操作步骤 通讯测试检查安装成果trace 文件下载 pcan使用 PCAN-Explorer 5安装 默认路径——all user——yes——next——finish PCAN-USB Pro…

洛谷P1024 [NOIP2001 提高组] 一元三次方程求解(优雅的暴力+二分,干净利落)

P1024 [NOIP2001 提高组] 一元三次方程求解 前言题目题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 题目分析注意事项 代码后话额外测试用例样例输入 #2样例输出 #2 王婆卖瓜 题目来源 前言 没有前言,可能因为作者忘了编辑 题目 题目描述 有形如&…

异常断电文件损坏docker服务异常处理

问题场景 我们在某地部署信控平台,当初是在产品研发早期,采取的还是Windows服务器部署虚拟机的方式使用virtualbox导入centos7虚拟机,虚拟机里运行docker服务,使用docker-compose统一管理客户今天上午反馈,昨天断电了…

Pygame游戏实战四:打砖块

介绍模块 本游戏使用的是由Pycharm中的pygame模块来实现的,也可以在python中运行。通过Pygame制作一个打砖块,通过击打砖块来得到更多的分数,看看这个是你小时候玩的游戏吗? 最小开发框架 详情请看此文章:Pygame游戏…

界面组件Telerik UI for WinForms中文教程 - 如何自定义应用程序文件窗口?

Telerik UI for WinForms包含了一个高度可定制的组件,它取代了.NET中默认的OpenFileDialog。在下一个更新版本中,会发布一个向对话框浏览器提那家自定义位置的请求功能,本文演示了这个和另一个自定义功能,它可以帮助用户在浏览文件…

【题解】2023 DTS算法竞赛集训 第1次

比赛地址&#xff1a;https://www.luogu.com.cn/contest/143650 P1319 压缩技术 https://www.luogu.com.cn/problem/P1319 简单的签到模拟题 #include <iostream>//c标准库 using namespace std; int main(){int a,n,t0,i0,b,s0;//t判断有没有回车&#xff0c;i判断输…

分支限界法求解迷宫问题

问题描述 从入口出发&#xff0c;按某一方向向前探索&#xff0c;若能走通(未走过的&#xff09;&#xff0c;即某处可以到达&#xff0c;则到达新点&#xff0c;否则试探下一方向&#xff1b;若该点所有的方向均没有通路&#xff0c;则沿原路返回到前一点&#xff0c;换下一个…

一台抵得上多种测量仪器-B1500A半导体参数分析仪

一台抵得上多种测量仪器-B1500A半导体参数分析仪 B1500A 半导体器件分析仪 卓越的测量能力&#xff0c; 完美的一体化解决方案&#xff0c; 经济高效, 出色的软件。 #B1500A 3步表征设备 使用B1500A半导体参数分析仪或PC上随附的EasyEXPERT group 表征软件。EasyEXPERT …

如何卸载在linux下通过rpm安装的mysql

目录 1.先关闭MySQL服务并查看运行状态 2.使用 rpm 管道命令的方式查看已安装的mysql 3. 使用rpm -ev 命令移除安装 4. 删除MySQL数据库内容 1.先关闭MySQL服务并查看运行状态 如果之前安装过并已经启动&#xff0c;则需要卸载前请先关闭MySQL服务 systemctl stop mysqld…

Juniper Networks Junos OS EX远程命令执行漏洞(CVE-2023-36845)

Juniper Networks Junos OS EX远程命令执行漏洞&#xff08;CVE-2023-36845&#xff09; 免责声明漏洞描述漏洞影响漏洞危害网络测绘Fofa: body"J-web" || title"Juniper Web Device Manager" 漏洞复现1. 构造poc2. 查看文件3. 执行命令 免责声明 仅用于技…

【编译原理】LL(1)文法

文章目录 语法分析基本概念自上而下语法分析自上而下语法分析的问题 消除文法左递归消除直接左递归消除间接左递归消除左递归的算法 解决回溯问题FIRST集与提出公共左因子FIRST集提取左公共因子 FOLLOW集合 构造FIRST集和FOLLOW集构造FIRST集合构造每个文法符号的FIRST集合构造…

新书稿费终于下来了!你猜有多少?

我的新书《从零开始学ARM》从正式出版到现在已经有半年时间了&#xff01; 第一批印刷的几千册已经基本销售完&#xff0c; 第二版会对其中勘误进行修正&#xff0c;并继续继续印刷。 前两年写书、审稿&#xff0c; 所有业余时间都耗在这上面了&#xff0c; 在下面这篇文章…

人大金仓KingbaseES_V008R006C008B0014安装

人大金仓安装 一、安装前准备工作 1、硬件环境要求 KingbaseES支持通用X86_64、龙芯、飞腾、鲲鹏等国产CPU硬件体系架构。 2、软件环境要求 KingbaseES支持各种主流的Linux操作系统64位发行版本&#xff0c;包括CentOS、中标麒麟、银河麒麟、统信UOS、Deepin、凝思、中科方…

基于springboot+vue开发的教师工作量管理系

教师工作量管理系 springboot31 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了教师工作量管理系统的开发全过程。通过分析教师工作量管理系统管理的不足&#xff0c;创建了一个计算机管理教师工作量管理系…

EtherCAT转EtherNET/IP协议网关控制EtherCAT伺服驱动器的方法

只需一步&#xff0c;将你的EtherCAT协议设备转换为EthernetIP协议&#xff01; 捷米特JM-ECTM-EIP网关&#xff0c;这款专为EtherCAT协议设备设计的转接装置&#xff0c;可以轻松地将EtherCAT设备数据采集的数据转换成EthernetIP协议。而且&#xff0c;我们的网关接口非常灵活…

同星智能亮相2023北美汽车测试展,国产替代的前方是“星辰大海”!

01 圆满落幕 2023年10月24日至10月26日&#xff0c;为期三天的2023北美汽车测试展览会&#xff08;Automotive Testing Expo&#xff09;在美国密歇根 Surburban Collection Showplace 成功举行。同星智能作为一家具备全球影响力的中国工业软件企业亮相了本次展会&#xff0c;…

聊一聊 tcp/ip 在.NET故障分析的重要性

一&#xff1a;背景 1. 讲故事 这段时间分析了几个和网络故障有关的.NET程序之后&#xff0c;真的越来越体会到计算机基础课的重要&#xff0c;比如 计算机网络 课&#xff0c;如果没有对 tcpip协议 的深刻理解&#xff0c;解决这些问题真的很难&#xff0c;因为你只能在高层…