[初阶数据结构】单链表

news2024/11/17 0:54:34

前言 

📚作者简介:爱编程的小马,正在学习C/C++,Linux及MySQL。

📚本文收录于初阶数据结构系列,本专栏主要是针对时间、空间复杂度,顺序表和链表、栈和队列、二叉树以及各类排序算法,持续更新!

📚相关专栏C++及Linux正在发展,敬请期待!

目录

前言 

1. 链表

1.1 链表的定义 

1.2 链表与顺序表相比的好处

1.3 链表的结构表示

1.3.1 链表的结构形式

1.3.2 链表的结构性质

1.4 单链表的实现

1.4.1 单链表的创建

 1.4.2 单链表的打印

1.4.3 单链表的动态内存申请

1.4.4 单链表的头插

1.4.5 单链表的尾插

1.4.6 单链表的头删

 1.4.7 单链表的尾删

1.4.8 单链表的查找

1.4.9 单链表的任意位置插入的前插

1.4.10 单链表任意位置的删除

2.单链表的完整代码

2.1 test.c测试函数代码

2.2 SList.h函数声明代码

2.3 SList.c函数实现代码

总结


1. 链表

1.1 链表的定义 

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序所决定的

本文只介绍最简单的链表结构:单链表

1.2 链表与顺序表相比的好处

1、顺序表从中间插入/头部插入,时间复杂度是O(N),因为要一次往后挪动,但是单链表是O(1),大大节省了程序运行时间。

2、 顺序表每次需要增容,到后期增容很大的时候,需要拷贝数据、开辟新空间、释放旧空间。会有不小的损耗。链表直接开辟一个结构体大小的空间即可。

3、增容一般是两倍,但是我就想多插入几个仅此而已,会造成空间的大规模浪费。链表同样更加简单且占用空间小。

1.3 链表的结构表示

首先要给大家介绍一下,就是链表中的结点是一个结构体,结点中一个变量是存储数据的,另一个变量是存储结构体地址的,上一个结点存下一个结点的地址,从而链接起来。

1.3.1 链表的结构形式

1.3.2 链表的结构性质

1、从上图可以看出,链表在逻辑上是连续的,在物理地址上是不连续的

2、现实的结点是动态内存在堆区申请出来的

3、堆上申请的空间,是按照一定的规律来的,有些可能相同,有些可能不同。

1.4 单链表的实现

1.4.1 单链表的创建

上文我们提到了,结点是一个结构体,第一个结构体变量是存储数据的,第二个是存储下一个链表的地址的

typedef int SListDataType;

typedef struct SListNode
{
	SListDataType data;
	struct SListNode* next;
}SLTNode;

 1.4.2 单链表的打印

void PrintSList(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
}

为什么是这样子打印的?给大家说一下思想:

1、单链表定义了最后一个链表的地址为空指针,所以我们就定义了一个cur来遍历整个链表

2、每找到一个数据我们就打印,然后遍历链表指针cur就往后走一步, 怎么走?是不是next中存放了下一个结点的地址,那么把cur管理的结构体中next的地址赋值给cur是不是相当于向后走了一步。

1.4.3 单链表的动态内存申请

SLTNode* BuySLTNode(SListDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

 比方说,我想申请一块动态内存空间,里面存储x的值,那么这个时候,就通过malloc在堆上申请一块空间,交给newnode管理,这时候把newnode中data的值赋值为x,newnode中next的值赋值为NULL后返回这块空间的地址。这是不是就很好的开辟了一个结点。如果开辟失败了就返回空指针。

1.4.4 单链表的头插

void SListPushFront(SLTNode** pphead, SListDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	assert(newnode);
	newnode->next = *pphead;
	*pphead = newnode;
}	

这里我们用newnode来管理开辟的新结点,如果开辟了失败了就不用往下了,开辟成功了往下走,我画个图来帮助大家理解上面代码的意思

1.4.5 单链表的尾插

我先给大家介绍一下,尾插有两种情况,第一种空链表,第二种,非空链表

先分析第一种情况,如果是空链表,是不是直接把newnode的地址给pphead是不是就可以了,

第二种情况,我给大家画个图一起来分析

void SListPushBack(SLTNode** pphead, SListDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	//空链表
	
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	//非空链表
	SLTNode* tail = *pphead;
	while (tail->next != NULL)
	{
		tail = tail->next;
	}
	tail->next = newnode;
}

第二种情况,首先我们要尾插,是不是要先找尾部在哪里?尾部在哪里?是不是说tail->next是空指针,这个就是链表中最后一个元素了,找到了之后,就把tail->next存newnode的地址就可以。

1.4.6 单链表的头删

给大家说一下啊,如何删除单链表中的数?是不是只需要让上个结点存下下个结点的地址就好了。

那么头删就是让*pphead指向下下个结点,然后释放第一个结点就好了。 

那么,应该这么做,看代码

void SListPopFront(SLTNode** pphead)
{
	assert(*pphead);
	
	//链表只有一个值
	SLTNode* cur = *pphead;
	*pphead = cur->next;
	free(cur);
	cur = NULL;
}

画个图给大家理解一下:

 1.4.7 单链表的尾删

尾删就更简单了,还是第一步,找尾,第二部,释放空间即可。

void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next->next != NULL)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}

1.4.8 单链表的查找

在单链表中查找一个数,找到了就返回这个结点的地址,没找到就返回空指针。

SLTNode* SListFind(SLTNode* pphead, SListDataType x)
{
	assert(pphead);
	SLTNode* cur = pphead;
	while (cur)
	{
		if (cur->data == x)
			return cur;
		else
			cur = cur->next;
	}
	return NULL;
}

1.4.9 单链表的任意位置插入的前插

void SListInsertbefore(SLTNode** pphead, SLTNode* pos, SListDataType x)
{
	if (*pphead == NULL)
	{
		SListPushFront(pphead, x);
	}
	//在pos前插入
	else
	{
		SLTNode* newnode = BuySLTNode(x);
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = newnode;
		newnode->next = pos;
	}
}

首先啊,如果pphead没有值,那么就相当于头插,如果链表中有值,画个图给大家理解

1.4.10 单链表任意位置的删除

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

2.单链表的完整代码

2.1 test.c测试函数代码

#include "SList.h"
SLTNode* Phead = NULL;
void Test1()
{
	//该函数测试头插和尾插
	SListPushFront(&Phead, 1);
	SListPushFront(&Phead, 2);
	SListPushFront(&Phead, 3);
	SListPushFront(&Phead, 4);
	SListPushFront(&Phead, 5);
	SListPushBack(&Phead, 2);
	SListPushBack(&Phead, 3);
	SListPushBack(&Phead, 4);
	PrintSList(Phead);
}

void Test2()
{
	//该函数测试头删和尾删
	SListPushFront(&Phead, 1);
	SListPopBack(&Phead);
	PrintSList(Phead);
}
void Test3()
{
	//查找
	SListPushFront(&Phead, 1);
	SListPushFront(&Phead, 2);
	SListPushFront(&Phead, 3);
	SListPushFront(&Phead, 4);
	SLTNode* find = SListFind(Phead, 3);
	if (find)
		find->data = 30;
	PrintSList(Phead);
}
void Test4()
{
	//任意位置插入(前插)
	SListPushFront(&Phead, 1);
	SListPushFront(&Phead, 2);
	SListPushFront(&Phead, 3);
	SListPushFront(&Phead, 4);
	SLTNode* find1 = SListFind(Phead, 3);
	SLTNode* find2 = SListFind(Phead, 4);
	if (find1)
	{
		SListInsertbefore(&Phead, find1, 40);
		SListEraseafter(find2);
		SListEraseafter(find1);
	}
	PrintSList(Phead);
}
int main()
{
	Test1();
	//Test2();
	//Test3();
	//Test4();
	return 0;
}

2.2 SList.h函数声明代码

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SListDataType;

typedef struct SListNode
{
	SListDataType data;
	struct SListNode* next;
}SLTNode;




void PrintSList(SLTNode* phead);
//头插
void SListPushFront(SLTNode** pphead, SListDataType x);
//尾插
void SListPushBack(SLTNode** pphead, SListDataType x);
//头删
void SListPopFront(SLTNode** pphead);
//尾删
void SListPopBack(SLTNode** pphead);
//查找
SLTNode* SListFind(SLTNode* pphead, SListDataType x);
//在pos之后插入x
void SListInsertbefore(SLTNode** pphead,SLTNode* pos, SListDataType x);
//在pos之后插入x
void SListInsertafter(SLTNode* pos, SListDataType x);
//删除pos位置上的值
void SListErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的位置
void SListEraseafter(SLTNode* pos);

2.3 SList.c函数实现代码



#include "SList.h"
void PrintSList(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
}

SLTNode* BuySLTNode(SListDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void SListPushFront(SLTNode** pphead, SListDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	assert(newnode);
	newnode->next = *pphead;
	*pphead = newnode;
}	


void SListPushBack(SLTNode** pphead, SListDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	//空链表
	
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	//非空链表
	SLTNode* tail = *pphead;
	while (tail->next != NULL)
	{
		tail = tail->next;
	}
	tail->next = newnode;
}

void SListPopFront(SLTNode** pphead)
{
	assert(*pphead);
	
	//链表只有一个值
	SLTNode* cur = *pphead;
	*pphead = cur->next;
	free(cur);
	cur = NULL;
}

void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next->next != NULL)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}

SLTNode* SListFind(SLTNode* pphead, SListDataType x)
{
	assert(pphead);
	SLTNode* cur = pphead;
	while (cur)
	{
		if (cur->data == x)
			return cur;
		else
			cur = cur->next;
	}
	return NULL;
}
void SListInsertbefore(SLTNode** pphead, SLTNode* pos, SListDataType x)
{
	if (*pphead == NULL)
	{
		SListPushFront(pphead, x);
	}
	//在pos前插入
	else
	{
		SLTNode* newnode = BuySLTNode(x);
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = newnode;
		newnode->next = pos;
	}
}

void SListInsertafter(SLTNode* pos, SListDataType x)
{
	assert(pos);
	//只有一个
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

void SListEraseafter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* cur = pos->next;
	pos->next = cur->next;
	free(cur);
	cur = NULL;
}

总结

1、单链表其实不难,大家一定要搞清楚指针和结构体

2、一定要动手实践一下

3、其实数据结构就是围绕着数据的增删查改显示这几个点,所以一定要搞清楚每一个代码实现的逻辑。

如果这份博客对大家有帮助,希望各位给小马一个大大的点赞鼓励一下,如果喜欢,请收藏一下,谢谢大家!!!
制作不易,如果大家有什么疑问或给小马的意见,欢迎评论区留言。

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

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

相关文章

【Python系列】字节串与字典字节串

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【高校科研前沿】北师大陈晋教授团队在遥感顶刊发表最新成果:ClearSCD模型:在高空间分辨率遥感影像中综合利用语义和变化关系进行语义变化检测

01文章简介 论文名称&#xff1a;The ClearSCD model: Comprehensively leveraging semantics and change relationships for semantic change detection in high spatial resolution remote sensing imagery&#xff08;ClearSCD模型&#xff1a;在高空间分辨率遥感影像中综合…

Nextcloud私有云盘-重新定义云存储体验

Nextcloud私有云盘-重新定义云存储体验 1. 什么是Nextcloud ​ Nextcloud是一个开源的云存储和协作平台&#xff0c;旨在为个人用户、企业和团队提供安全、隐私保护的数据存储和共享解决方案。它允许您在不同设备之间同步、共享文件&#xff0c;提供了强大的协作工具和应用生…

VSCode:设置顶部文件标签页滚动条的宽度

使用VSCode打开多个文件后&#xff0c;顶部的文件标签可以通过滚动条进行滚动&#xff0c;但是缺点是该滚动条太窄了&#xff0c;不好选择。 可以通过如下方法修改改滚动条的宽度&#xff1a; 1.点击设置 2.选择工作台->编辑管理->Title Scrollbar Sizing->Large 3.可…

MSP430环境搭建

1.下载ccs编译器 注意&#xff1a;安装路径和工作路径不能出现中文&#xff01; 没有说明的步骤就点next即可&#xff01; 1.1下载适合自己电脑的压缩包。 下载好压缩包后解压&#xff0c;点击有图标进行安装。 1.2创建一个文件夹用于安装编译器位置 选择安装地址&#xff0…

FFmpeg常用API与示例(四)——过滤器实战

1.filter 在多媒体处理中&#xff0c;filter 的意思是被编码到输出文件之前用来修改输入文件内容的一个软件工具。如&#xff1a;视频翻转&#xff0c;旋转&#xff0c;缩放等。 语法&#xff1a;[input_link_label1]… filter_nameparameters [output_link_label1]… 1、视…

Mybatis操作数据库的两种方式:原生API

mybatis操作数据的两种方式&#xff1a;原生api和mapper代理对象 1.mybatis的api提供的方法 insert() 增加 delete() 删除 update() 更新 selectOne() 返回一个数据 selectList() 返回多个数据&#xff0c;结果类型为List selectMap() 返回多个数据&…

YOLOv5-7.0改进(四)添加EMA注意力机制

前言 关于网络中注意力机制的改进有很多种&#xff0c;本篇内容从EMA注意力机制开始&#xff01; 往期回顾 YOLOv5-7.0改进&#xff08;一&#xff09;MobileNetv3替换主干网络 YOLOv5-7.0改进&#xff08;二&#xff09;BiFPN替换Neck网络 YOLOv5-7.0改进&#xff08;三&…

net 7部署到Linux并开启https

1.修改代码设置后台服务运行 安装nuget包 Install-Package Microsoft.Extensions.Hosting.WindowsServices Install-Package Microsoft.Extensions.Hosting.Systemd在Program代码中设置服务后台运行 var builder WebApplication.CreateBuilder(args);if (System.OperatingS…

x6.js bug记录-流程图json数据导入进来之后拖拽节点,节点直接飞走了

添加josn数据进来之后虽然能正常渲染&#xff0c;但是只要一拖拽&#xff0c;则节点就直接飞走了&#xff0c;看不到了。 找了一下午的问题&#xff0c;最后发现。保存的json坐标位置是字符串类型&#xff0c;而这边的位置必须是数字类型。如下&#xff1a; {position: { x: &…

词令蚂蚁新村今日答案:微信小程序怎么查看蚂蚁新村今天问题的正确答案?

微信小程序怎么查看蚂蚁新村今天问题的正确答案&#xff1f; 1、打开微信&#xff0c;点击搜索框&#xff1b; 2、打开搜索页面&#xff0c;选择小程序搜索&#xff1b; 3、在搜索框&#xff0c;输入词令搜索点击进入词令微信小程序&#xff1b; 4、打开词令微信小程序关键词口…

基于Matplotlib的模型性能可视化工作

一、项目简介 本项目是科技考古墓葬识别工作的中间过程&#xff0c;因为需要大量复用所以另起一章好了。 主要涉及到数据读取、数据可视化和少量的数据处理过程。 二、相关知识 PandasMatplotlib 三、实验过程 1. 数据探索性分析 1.1 准备工作–导入模块 import pandas…

考研入门55问---基础知识篇

考研入门55问---基础知识篇 01 &#xff1e;什么是研究生入学考试&#xff1f; 研究生是指大专和本科之后的深造课程。以研究生为最高学历, 研究生毕业后&#xff0c;也可称研究生&#xff0c;含义为研究生学历的人。在中国大陆地区&#xff0c;普通民众一般也将硕士毕业生称…

JavaSE基础小知识Ⅱ(很容易错!!!)

1. 变量被final修饰后不能再指向其他对象&#xff0c;但可以重写 如果是引用变量被final修饰&#xff0c;那么的确如此&#xff1b; 基本变量不能重写 2. 下列代码的输出结果是&#xff1f; public class Test {static {int x 5; }static int x,y; public static void ma…

conan2 基础入门(01)-介绍

conan2 基础入门(01)-介绍 文章目录 conan2 基础入门(01)-介绍⭐什么是conan官网Why use Conan? ⭐使用现状版本情况个人知名开源企业 ⭐ConanCenter包中心github ⭐说明文档END ⭐什么是conan 官网 官网&#xff1a;Conan 2.0: C and C Open Source Package Manager 一句话来…

【Linux:lesson1】的基本指令

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 &#x1f697;打开Xshell&#xff0c;登陆root…

个人网站快速搭建手册:低成本,高效率,轻松发布

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;React篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来-快速构建个人站|博客|系统,低成本发布上线 目录 前言 博主主页搭建案例 虚拟主机|服务器|域名 使用免费二级…

python实现九宫格图片切割

欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目录 一.前言 二.代码 三.使用 四.分析 一.前言 朋友圈九宫格是一种在社交媒体上展示图片或内容的常见布局方式,特别是在微信朋友圈中非常流行。这种布局由九个相同大小的格子组成,通常用于展示一组相关的图片或内容。&…

线程知识点

一、线程 1.定义 线程&#xff1a;是一个进程并发执行多种任务的机制。 串行&#xff1a;多个任务有序执行&#xff0c;一个任务执行完毕后&#xff0c;再去执行下一个任务 并发&#xff1a;多个任务在单个CPU上运行&#xff0c;同一个时间片上只能运行一个任务&#xff0c;c…

数据的均匀化分割算法(网格划分法、四叉树法(含C++代码))

数据的均匀化分割主要是指在分割过程中尽可能均匀地将数据点分布在各个子区域中&#xff0c;以保持数据分布的平衡和优化数据结构的性能。以下是几种可以实现数据均匀化分割的方法&#xff1a; 一. 网格划分法 1. 基本概念 虽然传统的网格划分法不是动态调整的&#xff0c;但通…