【数据结构】单链表(一)

news2025/9/21 5:59:55

上一篇【数据结构】顺序表-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));//创建节点1
	node1->data = 1;//赋值

	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));//创建节点2
	node2->data = 2;//赋值

	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));//创建节点3
	node3->data = 3;//赋值

	SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));//创建节点4
	node4->data = 4;//赋值
}
int main()
{
	SListtest1();
	return 0;
}

虽然我们创建好了节点,但是这几个节点现在还不能互相找到,所以我们接下来要将这几个节点连接起来,怎么连接?存下一个节点的地址

void SListtest1()
{
	SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));//创建节点1
	node1->data = 1;//赋值
	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));//创建节点2
	node2->data = 2;//赋值
	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));//创建节点3
	node3->data = 3;//赋值
	SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));//创建节点4
	node4->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不可以为NULL
	SLNode* 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不能为NULL
	SLNode* 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);//打印
}

下篇我们再继续介绍更多内容,拜拜~

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

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

相关文章

电商技术揭秘十八:电商平台的云计算与大数据应用小结

电商技术揭秘相关系列文章 电商技术揭秘一&#xff1a;电商架构设计与核心技术 电商技术揭秘二&#xff1a;电商平台推荐系统的实现与优化 电商技术揭秘三&#xff1a;电商平台的支付与结算系统 电商技术揭秘四&#xff1a;电商平台的物流管理系统 电商技术揭秘五&#xf…

日志监控思路分享,只监控日志内容,不存储

有一个这样的需求&#xff0c;就是实时监控日志文件的内容&#xff0c;不需要存储&#xff0c;仅当某行日志内容触发某个规则时调用一段业务逻辑就行了。比如用户触发限流规则&#xff0c;就将其封禁并发送钉钉通知到运维群。 看到这个需求首先想到的就是日志采集工具&#xff…

数据可视化的3D问题

三维对象非常流行&#xff0c;但在大多数情况下会对解释图形的准确性和速度产生负面影响。 以下是对涉及 3d 的主要图形类型的回顾&#xff0c;并讨论了它们是否被认为是不好的做法。 1、3D 条形图&#xff1a;不要 这是一个 3d 条形图。 你可能很熟悉这种图形&#xff0c;因为…

Windows上面搭建Flutter Android运行环境

Flutter Android环境搭建 电脑上面安装配置JDK电脑上下载安装Android Studio电脑上面下载配置Flutter Sdk &#xff08;避坑点一&#xff09;下载SDK配置对应的环境变量 到path 电脑上配置Flutter国内镜像运行 flutter doctor命令检测环境是否配置成功创建运行Flutter项目&…

网站HTTP升级成为HTTPS的方法

将网站从HTTP免费升级为HTTPS&#xff0c;您可以按照以下步骤操作&#xff1a; 1. 选择证书颁发机构&#xff08;CA&#xff09;&#xff1a; - 为了免费升级&#xff0c;您可以选择使用JoySSL这样的公益项目。JoySSL提供免费、自动化的SSL/TLS证书颁发服务&#xff0c;适用于各…

拉普拉斯IPO丨用创新科技助力中国光伏产业高质量发展

近年来&#xff0c;在“以科技创新引领现代化产业体系建设”的战略指引下&#xff0c;整个光伏行业持续推动技术迭代与生产力升级&#xff0c;朝着更高光电转化效率、更低成本加速迈进。 在此背景下&#xff0c;一批以技术驱动为第一生产力的光伏厂商们&#xff0c;在自身领域…

Java编程题 | 打印杨辉三角

大家可以关注一下专栏&#xff0c;方便大家需要的时候直接查找&#xff0c;专栏将持续更新~ 题目描述 打印出杨辉三角形&#xff08;要求打印出10行如下图&#xff09; 解题思路 初始化变量&#xff1a;设置一个变量表示行号&#xff0c;从1开始。循环打印杨辉三角形…

DSP笔记9-9. GPIO程序控制LED

GPIO23低电平点亮 周期性改变GPIO23电平&#xff0c;使得LED闪烁 void main(void) { InitSysCtrl(); //初始化系统时钟 InitGpio(); //初始化 GPI0 EALLOW; //允许保护 GpioCtrlRegs.GPAMUX2.bit.GPI0230; //将GP1023设置为GPIO GpioCtrlRegs.GPADIR.bit.GPI0231; //将GP1…

恒创科技:香港服务器CPU核心数如何选?越多越好吗?

​  谈到 CPU“核心”是完成所有处理的组件&#xff0c;程序能否顺利运行的第一因素是你有多少个核心。但由于不同的计算任务占用不同的资源&#xff0c;所以如果您打算简单地创建小型网站或者其他请求处理数据也不高的业务&#xff0c;那么您的基本型号应该包含 1、2 核已经…

康谋分享 | aiSim5 物理相机传感器模型验证方法(一)

摘要&#xff1a; aiSim5可以实时模拟复杂的传感器配置&#xff0c;在多GPU分布式渲支持的支持下&#xff0c;aiSim可以渲染20多个摄像头、10多个雷达和10多个激光雷达在同一环境下运行。aiSim5独有的实时渲染引擎能够满足对物理精确环境和天气模拟的所有要求&#xff0c;具有…

RUKOTA 网络ip对讲系统

RUKOTA 网络ip对讲系统 IP对讲采用TCP/IP技术, 将音频信号以数据包形式在局域网和广域网上进行传送&#xff0c;是一套纯数字传输的免提对讲系统&#xff0c;解决了传统对讲系统存在的传输距离有限、易受干扰等问题。 IP对讲系统结构简捷, 只需将终端接入计算机网络即可构成功…

AcWing---游戏---区间dp

1388. 游戏 - AcWing题库 思路&#xff1a; 两个人比赛&#xff0c;是一道博弈论问题&#xff0c;主要思想就是A-B取到最大值。A是我方得到的分数&#xff0c;B是对方得到的分数。我们设g[i][j]是从第i个数到第j个数&#xff0c;先手-后手取得的最大值&#xff0c;分类讨论&a…

【教学类-50-06】20240410“数一数”4类星号图片制作PDF学具

作品展示&#xff1a; 背景需求&#xff1a; 前文遍历四个文件夹&#xff0c;分别将每个文件夹内的10个图片的左上角加入星号&#xff0c;显示难度系数 【教学类-50-05】20240410“数一数”4类图片添加“难度星号”-CSDN博客文章浏览阅读55次&#xff0c;点赞2次&#xff0c;…

ctfshow--web入门--文件上传--web168--web170

web168 法一免杀脚本 还是检查&#xff0c;准备上传图片马 我写的是<?php eval($_POST[a]);?> 上传之后没反应 那么查一下&#xff0c;原来是发现对eval,system还有$_POST和$_GET进行过滤,$_REQUEST还可以用 那么再写一个马&#xff08;免杀脚本&#xff09; <?…

lovesql 手工sql注入

1.页面 2.万能密码登录成功 我还傻乎乎的以为密码就是flag 但不是 3. 继续注入 判断列数 确定了只有三列 开始尝试联合注入 4.使用联合注入之前先判断显示位 5.之后一步一步的构造&#xff0c;先得到当前数据库名 利用database&#xff08;&#xff09; 再得到库里有哪些表 …

vue3中使用antv-S2表格(基础功能版)

先看展示效果&#xff1a; 可以调整行宽、列宽、自定义字段图标、表头图标、添加排序、显示总计、小计等 首先确保搭建一个vue3项目环境&#xff0c;从0开始的小伙伴着重看第一点&#xff1a; 一、搭建vue3项目环境 首先创建一个vue3vitets项目&#xff0c;可以查看下面相关…

windows + pytorch + gpu

nvidia、cuda和cudnn之间的版本匹配关系 1. 命令行查看nvidia的驱动版本 英伟达驱动版本是512.78&#xff0c;cuda版本是11.6&#xff08;说明要下的cuda toolkit版本最高是11.6的&#xff09; 插入&#xff1a;显卡驱动的安装 2. 网站查看cuda和英伟达驱动、cudnn之间的关系…

JVM面试整理--对象的创建和堆

文章目录 对象的创建过程是怎样的?对象在内存中的结构是怎样的&#xff08;专业的叫法&#xff1a;对象的内存布局&#xff09;对象在内存分配时使用的哪种方式&#xff08;有的地方也称为&#xff1a;分配算法&#xff09;知道什么是“指针碰撞”吗&#xff1f;知道什么是“空…

PlayerSettings.WebGL.emscriptenArgs设置无效的问题

1&#xff09;PlayerSettings.WebGL.emscriptenArgs设置无效的问题 2&#xff09;多个小资源包合并为大资源包的疑问 3&#xff09;AssetBundle在移动设备上丢失 4&#xff09;Unity云渲染插件RenderStreaming&#xff0c;如何实现多用户分别有独立的操作 这是第381篇UWA技术知…

“盲人辅助”科技革新:软件赋能,破解出行难题

作为一名资深记者&#xff0c;我长期关注并报道社会弱势群体权益保障议题&#xff0c;其中视障人士的出行困境尤为引人深思。面对城市生活的复杂环境&#xff0c;盲人辅助技术的革新显得至关重要。近期&#xff0c;一款专注于辅助盲人避障的应用蝙蝠避障脱颖而出&#xff0c;以…