SList(单链表)

文章目录

    • 一:线性表
    • 二:数组
      • 2.1数组在内存中的存储
    • 三:链式结构
    • 四:单链表
      • 4.1概念与结构
        • 4.1.1概念
        • 4.1.2 结构(节点)
        • 4.1.3链表的性质
        • 4.1.4链表的打印
      • 4.2实现单链表
    • 结语

欢迎大家来到我的博客,给生活来点impetus。
在这里插入图片描述今天我们来学习单链表

在学习单链表之前,我们先了解线性表
为什么呢?首先举一个例子,在生活中了解苹果和梨首先得知道他是水果。
同理,单链表(SList)也是线性表的一种。

一:线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是⼀种在实际中⼴泛使⽤的
数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

二:数组

2.1数组在内存中的存储

我们先来看一段代码:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("&arr[%d] = %p\n ", i, &arr[i]);
	}
	return 0;
}

再来看结果:
在这里插入图片描述
因为int(整形)占4个字节,所以每个数组匀元素的地址相差4。
从这里我们可以推出:

数组在内存中的存储是连续的

图像如下:
在这里插入图片描述
扩展:顺序表的底层逻辑就是数组。

三:链式结构

在数据结构中,链式结构是一种存储数据的方式。

它由节点组成,每个节点包含数据部分和指向下一个节点的指针(在单链表中)。就像火车车厢,每节车厢装着货物(数据),还有连接下一节车厢的挂钩(指针)。
在这里插入图片描述
在这里插入图片描述

四:单链表

4.1概念与结构

4.1.1概念

链表是⼀种物理存储结构⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

4.1.2 结构(节点)

与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/结点”,结点的组成主要有两个部分:

当前结点要保存的数据和保存下⼀个结点的地址(指针变量)

图中指针变量plist保存的是第⼀个结点的地址,我们称plist此时“指向”第⼀个结点,如果我们希望plist“指向”第⼆个结点时,只需要修改plist保存的内容为0x0012FFA0

链表中每个结点都是独⽴申请的(即需要插⼊数据时才去申请⼀块结点的空间),我们需要通过指针变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点

使用链表大大降低了内存的消耗,但是每次需要就会去开辟空间,大大降低了程序的执行效率

4.1.3链表的性质

1、链式机构在逻辑上是连续的,在物理结构上不⼀定连续
2、结点⼀般是从上申请的
3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续。

结合前⾯学到的结构体知识,我们可以给出每个结点对应的结构体代码:

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;//存储的数据多样,为了能够容易改变数据类型,重定义int型
	struct SListNode* next;
}SLTNode;

当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数据,也需要保存下⼀个结点的地址(当下⼀个结点为空时保存的地址为空)
当我们想要从第⼀个结点⾛到最后⼀个结点时,只需要在当前结点拿上下⼀个结点的地址就可以了。

4.1.4链表的打印

给定的链表结构中,如何实现结点从头到尾的打印?
在这里插入图片描述

4.2实现单链表

在一种数据结构中,我们会对数据进行增删查改。下面我们来书写一下这些代码。

细节
1:参数是否需要传递头结点的参数取决于是否遍历,影响上节点要遍历(再来设一个指针为了能够找到头结点),影响下节点不遍历直接pos=pos->next即可
2:传一级指针or二级指针,看该操作是否会改变头结点的指向位置,会(二级),反之一级
3;指定位置之前比指定位置之后传递的参数多一个,因为会涉及遍历,需要传递指向头结点的参数
4;断言pos而非*pphead,若影响pos之后不遍历,根本不会传递pphead或phead参数,所以就统一断言pos

我们用对链表进行操作来说明这些细节:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总代码:

SList.c

#include"SList.h"
//打印链表
//细节1:限制条件 2:地址不连续,不要使用自增自减,应该使用赋值语句找到下一个节点
void SLTPrint(SLTNode* phead)//效果,1->2->3->...->NULL
{
	SLTNode* pcur = phead;
	while (pcur != NULL)//如果我写成pcur->next!=NULL的话,把下面的那句代码移上来,会导致最后一个节点的数据遍历不到,具体的话自己画图来理解

	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
//创建节点并完成初始化
//细节1:这里需要返回他的地址,提供给后面代码使用2:判断空间是否开辟成功
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//这里不要再写structure了,因为我已经重定义了,否则就是 struct struct ListNode
	if (newnode == NULL)
	{//开辟不成功
		perror("malloc failed!");
		exit(1);
	}
	//开辟成功+初始化
	newnode->data = x;
	newnode->next = NULL;//不赋值的话现在就就是野指针,需要NULL去管理野指针
	return newnode;//因为我要插入的是节点,而不是整形数据,所以必须返回节点的地址
}
//链表尾插数据
//细节:这里需要用到二级指针,对形参的改变需要影响实参
//如果这样写,传值调用(void SLTPushBack(SLTNode* phead, SLTDataType x))
//出了函数过后,phead变量被销毁,只有对形参改变,而plist实参还是NULL并没有改变,打印的结果就只有NULL了
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = SLTBuyNode(x);
	//如果链表为空,phead就直接指向newnode,此时newnode是首节点
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//链表不为空,找尾结点,将尾结点和新节点连接起来
		SLTNode* ptail = *pphead;//这里为什么要有这条赋值语句?
		//因为*pphead==plist,若用*pphead去遍历,导致plist跟着改变,打印时传的是plist,导致打印的时候找不到头节点
		while (ptail->next)//等价于ptail->next != NULL
		{
			ptail = ptail->next;
		}
		//出循环代表ptail此时是NULL,将其赋值为下一个节点的地址
		ptail->next = newnode;
	}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//防止传过来的是一个NULL
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;//头结点的next指向下一个节点的地址
	*pphead = newnode;//头结点往前去一个
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	if ((*pphead)->next == NULL)//在这里,->优先级比*高,所以要加一个()
	{
		//此时链表中还有一个节点,prev不可能在往前,因为初始值为NULL,不能够对prev指针访问操作,不能对空指针解引用
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//此时链表中的节点>1个,能够找到prev,此时prev不为NULL
		SLTNode* prev = NULL;
		SLTNode* ptail = *pphead;
		while (ptail->next != NULL)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		//prev ptail
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}
}
//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur != NULL)//同SLTPrint一样这样写为了能够遍历到所有节点中的元素,如写为pcur->next!=NULL,最后一个节点数据访问不到
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//未找到
	return NULL;
}
//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && pos);
	if (pos==*pphead)//(*pphead)->next == NULL
	{
		//头插,此时只有一个节点,找不到pos,prev死循环,这种情况单独讨论
		SLTPushFront(pphead, x);
	}
	else
	{
		//>一个节点,此时可以找到prev
		//pos在头结点之后--->找pos前驱节点
		SLTNode* newnode = SLTBuyNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)//如果此时只有一个节点,prev会陷入死循环,因为开始prev==pos,prev在向后走,找不到pos,这种情况单独讨论
		{
			prev = prev->next;
		}
		//在这里先处理左边的节点,或先处理右边的节点最后的效果是一样的,为了统一下方,都先处理右方的节点
		newnode->next = pos;
		prev->next = newnode;
	}
}
//在指定位置之后插⼊数据(这种不可能是头插了)
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	//这里与上一个方法区别,这里必须先处理右边的节点,如果先处理左边的节点,在处理右边时此时指向的是newnode,右边的节点就找不到了
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	if (pos == *pphead)
	{
		//此时只有一个节点,头删
		SLTPopFront(pphead);
	}
	else
	{
		//此时节点数>1,可以找到prev,进而将pos之前的节点与之后的节点联系起来
		SLTNode* prev = *pphead;
		while (prev->next != pos)//这里只要找到pos即可,是prev在pos的前面一个
		{
			prev = prev->next;
		}
		//prev pos pos->next
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);//要有数据可删,pos下一个数据也可删
	SLTNode* del = pos->next;
	//pos del del->next
	pos->next = del->next;
	free(del);
	del = NULL;
}

//销毁链表
//细节:从后删的话,删除的上一个节点就找不到了,所以最好从前往后删,代码跟简洁
void SListDestroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* pcur = *pphead;
	while (pcur!=NULL)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

结语

感谢大家认真倾听我的博客,山高路远,仍需前行。成为最好的自己。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VTK知识学习(21)- 数据的读写

1、前言 对于应用程序而言&#xff0c;都需要处理特定的数据&#xff0c;VTK应用程序也不例外。 VTK应用程序所需的数据可以通过两种途径获取: 第一种是生成模型&#xff0c;然后处理这些模型数据(如由类 vtkCylinderSource 生成的多边形数据); 第二种是从外部存储介质里导…

Nignx部署Java服务测试使用的Spring Boot项目Demo

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

虚幻引擎生存建造系统

先做一个建造预览模式&#xff0c;按下按键B后进入建造预览模式 首先创建自定义事件Preview Loop 用射线追踪摆放物体预览位置&#xff0c;并做一个预览材质 增强输入设置按键 每帧判断是否进入建造模式 预览模式制作成功&#xff01; 接着做点击左键放置物品&#xff0…

Blender中使用BlenderGIS插件快速生成城市建筑模型

导入下载 BlenderGIS 插件 去github上下载其压缩包&#xff0c;地址如下&#xff1a; https://github.com/domlysz/BlenderGIS 在BlenderGIS中导入这个插件压缩包&#xff1a; 点击上方菜单栏的编辑&#xff0c;点击偏好设置 在插件>从磁盘安装中导入刚刚下载的压缩包 可…

C语言期末复习

1、任意输入一个半径给r&#xff0c;求圆的面积。 #include <stdio.h> #include <windows.h> void main() { double r,s; printf("输入一个半径给r"); scanf("%lf",&r); sr*r*3.1415926; printf("%lf",s); system(&qu…

深圳大学《2024年904自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《深圳大学904自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2024年真题 Part1&#xff1a;2024年完整版真题 2024年真题

transformers生成式对话机器人

简介 生成式对话机器人是一种先进的人工智能系统&#xff0c;它能够通过学习大量的自然语言数据来模拟人类进行开放、连贯且创造性的对话。与基于规则或检索式的聊天机器人不同&#xff0c;生成式对话机器人并不局限于预定义的回答集&#xff0c;而是可以根据对话上下文动态地…

NanoLog起步笔记-4-Server端的两个线程

nonolog起步笔记-4-Server端的两个线程 Server端的两个线程两个线程的角色与各自的职责RuntimeLogger::compressionThreadMain线程 详细学习一下相关的代码第三个线程第一次出现原位置swip buffer Server端的两个线程 如前所述&#xff0c;nanolog的server端&#xff0c;相对而…

Freertos任务切换

一、操作系统进行任务切换的时机&#xff1a; 采用信号量实现任务的互斥&#xff1a; 二、FreeRTOS 任务切换场合 PendSV 中断的时候提到了上下文(任务)切换被触发的场合&#xff1a; ● 可以执行一个系统调用 ● 系统滴答定时器(SysTick)中断。 1、执行系统调用 执行系统…

【硬件测试】基于FPGA的4FSK调制解调通信系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR

目录 1.算法仿真效果 2.算法涉及理论知识概要 3.Verilog核心程序 4.开发板使用说明和如何移植不同的开发板 5.完整算法代码文件获得 1.算法仿真效果 本文是之前写的文章: 《基于FPGA的4FSK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR》 的…

【Vue2+Element-ui】el-dialog宽度适配

1、不适配问题 分辨率100%-页面 分辨率150%-页面 在项目中&#xff0c;我开发分辨率一直是100%&#xff0c;但是客户使用的分辨率不相同&#xff0c;所以宽度要适配 2、解决-封装mixins.js 1)、封装的mixins 我将宽度设置成动态的&#xff0c;因为我的项目中需求不同。 expor…

Tr0ll: 1 Vulnhub靶机渗透笔记

Tr0ll: 1 本博客提供的所有信息仅供学习和研究目的&#xff0c;旨在提高读者的网络安全意识和技术能力。请在合法合规的前提下使用本文中提供的任何技术、方法或工具。如果您选择使用本博客中的任何信息进行非法活动&#xff0c;您将独自承担全部法律责任。本博客明确表示不支…

23. C++STL 9 (priority_queue的使用和适配实现详解)

⭐本篇重点&#xff1a; 1 priority_queue的使用与底层原理 2 使用容器来适配 priority_queue ⭐本篇代码&#xff1a;c学习 橘子真甜/c-learning-of-yzc - 码云 - 开源中国 (gitee.com) ⭐标⭐是比较重要的部分 目录 一. priority_queue&#xff08;优先级队列&#xff09;的…

十四、Pod的升级和回滚

当集群中的某个服务需要升级时,我们需要停止目前与该服务相关的所有Pod,然后下载新版本镜像并创建新的Pod。如果集群规模比较大,则这个工作变成了一个挑战,而且先全部停止然后逐步升级的方式会导致较长时间的服务不可用。Kubernetes提供了滚动升级功能来解决上述问题。 如…

中间件--MongoDB部署及初始化js脚本(docker部署,docker-entrypoint-initdb.d,数据迁移,自动化部署)

一、概述 MongoDB是一种常见的Nosql数据库&#xff08;非关系型数据库&#xff09;&#xff0c;以文档&#xff08;Document&#xff09;的形式存储数据。是非关系型数据库中最像关系型数据库的一种。本篇主要介绍下部署和数据迁移。 在 MongoDB 官方镜像部署介绍中&#xff…

MES系统通过eDrawings Pro API开发图纸批量转换工具,实现3D在线查看

声明&#xff1a;部分代码来源于网络&#xff0c;如有疑问&#xff0c;请联系本人删除。 通过C#结合eDrawings API提供接口&#xff0c;实现图纸转换为换.jpg、.tif、.bmp、.stl、.exe、.html、.zip、.edrw、.eprt 和 .eas格式工具&#xff0c;尤其是.html格式&#xff0c;可以…

Java阶段三06

第3章-第6节 一、知识点 理解MVC三层模型、理解什么是SpringMVC、理解SpringMVC的工作流程、了解springMVC和Struts2的区别、学会使用SpringMVC封装不同请求、接收参数 二、目标 理解MVC三层模型 理解什么是SpringMVC 理解SpringMVC的工作流程 学会使用SpringMVC封装请求…

【计算机网络】期末速成(2)

部分内容来源于网络&#xff0c;侵删~ 第五章 传输层 概述 传输层提供进程和进程之间的逻辑通信&#xff0c;靠**套接字Socket(主机IP地址&#xff0c;端口号)**找到应用进程。 传输层会对收到的报文进行差错检测。 比特流(物理层)-> 数据帧(数据链路层) -> 分组 / I…

word poi-tl 表格功能增强,实现表格功能垂直合并

目录 问题解决问题poi-tl介绍 功能实现引入依赖模版代码效果图 附加&#xff08;插件实现&#xff09;MergeColumnData 对象MergeGroupData 类ServerMergeTableData 数据信息ServerMergeTablePolicy 合并插件 问题 由于在开发功能需求中&#xff0c;word文档需要垂直合并表格&…

记一次:使用C#创建一个串口工具

前言&#xff1a;公司的上位机打不开串口&#xff0c;发送的时候设备总是关机&#xff0c;因为和这个同事关系比较好&#xff0c;编写这款软件是用C#编写的&#xff0c;于是乎帮着解决了一下&#xff08;是真解决了&#xff09;&#xff0c;然后整理了一下自己的笔记 一、开发…