单链表的实现

news2024/11/24 2:35:21

链表的概念与结构

链表与我们通讯录中的顺序表是不同的,顺序表的空间是连续的,像数组一样可以通过下标访问。而链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。即:链表是通过指针将一块块结构体(节点)串起来,前一个节点存放着指向后一个节点的地址。

 链表的理解

 链表就是像这样的,即将你想要存放的数据分别放在上面的1 2 3 4...中,因为在空间上是不连续的,但是若想要在逻辑上连续则需要使每一个节点中存放指向下一个节点的结构体指针,而链表的另一个特点就是:你要存多少数据就给你开辟多少节点的空间给你使用,不存在浪费空间的情况,即需要我们动态开辟空间(在堆区)

 单链表的功能实现

创建节点与链表头

	Test* plist = NULL; //链表头

 这里的链表头就是指向第一个节点的结构体指针,好一个一个的往后找数据。

typedef struct test
{
	int date;
	struct test* next;
}Test;

变量 data 就是存放我们要添加的数据 (我这里是整型),而 next 指针变量存放的就是下一个节点的地址,因为节点的类型是 struct test,所以我们就要创建同类型的指针接受。

Test* Malloc(int x)//x是要存放的数据
{
	Test* new = (Test*)malloc(sizeof(Test));
	assert(new);
	new->date = x;
	new->next = NULL;
	return new;
}

链表的节点是 malloc 动态开辟出来的,用多少就开辟多少。

打印数据

void Print(Test* phead)
{
	Test* cur = phead;//指向当前结构体的指针
	while (cur != NULL)//最后一个数据指针指向NULL
	{
		printf("%d->", cur->date);
		cur = cur->next;//指向下一个节点
	}
	printf("NULL");
}

链表和顺序表是不同的,顺序表可以通过数组下标去任意的访问数据,因为物理空间连续,链表的物理空间不连续,所以只能通过链表头从前往后的去一个个访问数据。

 cur 是我们创建的一个临时变量指向第一个节点,执行完之后就将 cur->next 赋给 cur 即:cur 指向下一个节点的地址...直到 cur 指向 NULL 就停止。

 头部插入数据

Test* Malloc(int x)//x是要存放的数据
{
	Test* new = (Test*)malloc(sizeof(Test));
	assert(new);
	new->date = x;
	new->next = NULL;
	return new;
}
void Pushfront(Test** phead,int x)
{
	Test* new = Malloc(x);
	new->next = *phead;

	*phead = new;
}

这里是从头部插入数据,所以会将链表头指向的地址发生改变,即改变指针,所以传参就要穿指针的地址,所以我这里用二级指针来接受数据,解引用就相当于直接访问链表头 plist 这个一级指针,改变 *phead 就相当于直接改变 plist。 Malloc(x)是我们开辟的节点地址即一个新的节点。

new->next = *phead; 即使新节点的next指向原来的链表头     *phead = new; “换头”

尾部插入数据

void Pushback(Test** phead, int x)
{
	Test* new = Malloc(x);//创建结构体

	if (*phead == NULL)//如果一个节点都没有
	{
		*phead = new;
		return;
	}
	//Test** cur = phead; //有问题,接下*cur也就相当于*phead,是同一块空间

	Test* cur = *phead; //临时空间
	while (cur->next != NULL)//找到尾部
	{
		cur = cur->next;
	}
	cur->next = new;//改变结构体用结构体指针

}

Test** cur = phead; //这个是博主自己犯的一个错误,这样可是行不通的哈,因为如果后续*cur去访问的可就不是临时变量了,解引用就相当于直接访问了 *phead 即:plist,这样进行后续的操作的话,就直接改变了链表头的指向了。所以就有 Test* cur = *phead; 这种写法cur是临时创建的变量改变 cur 并不会造成什么其他的影响,出了作用域也就销毁了。

 while (cur->next != NULL) 这里是通过 cur->next找尾结点,可不能用 cur 去直接找

 如果直接找就变成上面的样子,而且单链表是从前往后找的(一去不返)所以就有 cur->next 为空指针就停下来了。

指定位置插入数据

void InsertAfter(Test* pos, int x)//pos就是要插入的节点地址
{
	assert(pos);
	Test* tail = pos->next;//找到要增数据的下一个节点地址
	Test* new = Malloc(x);
	pos->next = new;
	new->next = tail;
}

 我们一般是在指定位置的后面插入节点的,所以就要先将要插入数据后面的一个节点链接起来,防止先插入数据导致后面的数据丢失。

如果我们要在指定位置前面插入数据的话就要在创建一个结构体指针指向要插入数据的前面一个节点的地址,并使 next 指向新开辟的节点地址。具体方法就留个你们试试了!

 尾部删除数据

void Popback(Test** phead)
{
	assert(*phead);
	if ((*phead)->next == NULL)//只有一个节点时
	{
		free(*phead);
		*phead = NULL;
		return;
	}
	Test* cur = *phead; //临时空间1
	
	while (cur->next->next != NULL)//找倒数第二个节点
	{
		cur = cur->next;
	}
	free(cur->next);//释放最后一个节点空间
	cur->next = NULL;
	
	//或者用两个临时空间寻找最后两个节点
	//Test* tail = *phead; //临时空间2
	//while (cur->next != NULL)
	//{
	//	tail = cur;
	//	cur = cur->next;
	//}
	//free(cur);
	//tail->next = NULL;

}

想要尾部数据删除就要将最后一个节点的空间释放,倒数第二个节点的 next 指向NULL

所以我们要找最后两个节点的空间。

方法1:直接通过一个结构体指针指向下下个节点

方法2:使用两个结构体指针一前一后

这里如果只有一个节点的话删除数据就要穿 plist 的地址了,因为会将 plist 置空,但如果不止一个节点的话,就不用传地址了,链表头的指向并不会改变。

头部删除数据 

void Popfront(Test** phead)
{
	assert(*phead);
	Test* cur = *phead; //临时空间
	*phead = cur->next;
	free(cur);

}

 删除链表的第一个数据就需要传链表头的地址,用二级指针接收,因为删除第一个节点后,链表头就要指向第二个节点的地址,所以解引用是直接对链表头 plist 存的数据进行直接操作。

指定位置删除数据

void EraseAfter(Test* pos)
{
	assert(pos);
	Test* cur = pos->next;//找到要删数据的下一个节点
	assert(cur);
	pos->next = cur->next;//将下下个节点串起来
	free(cur);//删除
}

 我们一般也是在指定位置的后面删除节点的,所以就要先将要删除数据后面的一个节点存起来起来,防止先释放空间导致后面的数据丢失。

如果我们要在指定位置前面删除数据的话也要在创建一个结构体指针指向要插入数据的前面一个节点的地址,具体实现就留给你们了。

 查链表数据

Test* Find(Test* phead, int x)
{
  
	Test* cur = phead;//指向当前结构体的指针
	while (cur->date != x && cur)//找到了或找完了就会退出
	{
		cur = cur->next;
	}
	if (cur->date == x)//判断是否找到
		return cur;
	return NULL;//没找到就返回空
}

查找数据直接遍历链表就可以了。但是这种写法,这里会有一个小问题,如果链表为空,则执行到while (cur->date != x && cur) 时就会报错了,cur->date 相当于解引用,为空时就会出错。所以下面这种写法就更安全。

Test* Find(Test* phead, int x)
{
	Test* cur = phead;//指向当前结构体的指针
	while (cur)
	{
		if (cur->date == x)//判断是否找到
			return cur;
		cur = cur->next;
	}
	return NULL;//没找到就返回空
}

空间释放

我们知道堆区开辟的空间要及时释放,否则会造成内存泄漏,十分危险!

void Free(Test* phead)
{
	Test* cur = NULL;//指向当前结构体的指针
	while (cur)
	{
		cur = phead;
		phead = phead->next;//先将后面的节点记下来
		free(cur);
	}
}

若有解释不当请留下评论相互学习!

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

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

相关文章

数据结构笔记:二叉树的遍历与技巧

引言 本篇是最近有遇到过的一个题目,关于二叉树的遍历,正好有一些经验与技巧,所以开一篇帖子记录一下。 二叉树遍历介绍 遍历是数据结构中常见的操作,主要是将所有元素都访问一遍。对于线性结构来说,遍历分为两种&a…

RecyclerView 静态布局实现过程解析:如何构建高性能的列表

作者:maxcion Recyclerview在日常开发中所使用的控件中绝对是顶流一般的存在,想嚼它这个想法一次两次了。在网上也看了很多关于Recyclerview源码解析的文章,大佬们写的都很深刻,但是对于像我们这种储备知识不足的小白读者来说&…

前端实现端到端测试(代码版)

端到端测试框架选取 playwright 、 cypress 、 selenium 对比 cypress使用 下载 cypress npm install cypress --save-dev package.json npm run cypress:open {"scripts": {"cypress:open": "cypress open"} }使用流程 入门官方文档 npm ru…

一本通 3.4.5 最小生成树

1348:【例4-9】城市公交网建设问题 【题目描述】 有一张城市地图,图中的顶点为城市,无向边代表两个城市间的连通关系,边上的权为在这两个城市之间修建高速公路的造价,研究后发现,这个地图有一个特点&…

SQL Server基础 第四章 select定制查询(select中的各种查询筛选条件)

本章主要介绍 select 语句查询数据的基本用法,其中包括查询指定字段信息、条件查询等。 目录 1、比较运算符、逻辑运算符 (1)查询phone大于500且不是单县的 (2)查询地址为烟台或者单县但是phone要大于666的 &#…

IMX6ull 之 HelloWorld Led点灯

一 GPIO点灯,嵌入式的helloworld 1 何为GPIO? GPIO只是一个CPU内提供的一种功能外设,CPU外部的I/O引脚会被赋予一种功能(GPIO、UART、I2C等);该功能由CPU内外设提供,具体是什么功能由IOMUX…

刷题笔记4-22

目录 1.Java:(a,b)>Math.abs(a-3)-Math.abs(b-3); 2.字符解释 3.C语言二维数组中a[i]表示ai的地址,而a[i]又可以表示为*(ai) 4.二维数组在传参时,必须给定列 5.软件开发:观察者模式 6.建…

shell脚本控制

shell脚本编程系列 处理信号 Linux利用信号与系统中的进程进行通信,通过对脚本进行编程,使其在收到特定信号时执行某些命令,从而控制shell脚本的操作。 Linux信号 shell脚本编程会遇到的最常见的Linux系统信号如下表所示: 在默…

【ros】6.ros激光雷达SLAM(建图定位)

百行业为先 ,万恶懒为首。——梁启超 文章目录 :smirk:1. 激光SLAM:blush:2. 二维激光SLAM:satisfied:3. 三维激光SLAM 😏1. 激光SLAM SLAM(同步定位与地图构建)是一种机器人感知技术,用于在未知环境中同时确定机器人…

java调用webservicer的方法

对于使用 Webservicer的方式,一般采用 Java API调用的方式。Webservicer是一个运行在浏览器中的客户端程序,它可以通过 Webservicer的接口来访问服务器上的服务。 使用 Java调用 Webservicer有两种方式: 下面是一个简单的例子: 2、…

零基础,零成本,部署一个属于你的大模型

前言 看了那么多chatGPT的文章,作为一名不精通算法的开发,也对大模型心痒痒。但想要部署自己的大模型,且不说没有算法相关的经验了,光是大模型占用的算力资源,手头的个人电脑其实也很难独立部署。就算使用算法压缩后的…

数据结构和算法学习记录——小习题-二叉树的遍历二叉搜索树

目录 二叉树的遍历 1-1 1-2 1-3 二叉搜索树 2-1 2-2 2-3 2-4 答案区 二叉树的遍历 1-1 假定只有四个结点A、B、C、D的二叉树,其前序遍历序列为ABCD,则下面哪个序列是不可能的中序遍历序列? .ABCD .ACDB .DCBA .DABC 1-2 对于…

最精简:windows环境安装tensorflow-gpu-2.10.1

Tensorflow 2.10是最后一个在本地windows上支持GPU的版本 1. 通过.whl文件方式安装2.创建anaconda虚拟环境3.安装对应的cuda与cudnn版本,local不必装cuda和cudnn4. 测试tensorflow gpu是否可用 1. 通过.whl文件方式安装 .whl文件的下载地址: tensorflow…

windows下使用vite创建vue项目

windows下使用vite创建vue项目 1 下载安装配置NodeJS1.1 下载1.2 安装1.3 配置1.4 npm镜像加速配置1.6 设置环境变量 2 Vite简单介绍3 Vite创建vue项目3.1 vite创建vue项目的命令3.2 vite创建vue项目步骤 1 下载安装配置NodeJS 1.1 下载 下载地址:https://nodejs.…

全注解下的SpringIoc 续2-bean的生命周期

spring中bean的生命周期 上一个小节梳理了一下Spring Boot的依赖注入的基本知识,今天来梳理一下spring中bean的生命周期。 下面,让我们一起看看bean在IOC容器中是怎么被创建和销毁的。 bean的生命周期大致分为四个部分: #mermaid-svg-GFXNEU…

数据分类分级 数据识别-识别日期类型数据

前面针对数据安全-数据分类分级方案设计做了分析讲解,具体内容可点击数据安全-数据分类分级方案设计,不再做赘述 上面图片是AI创作生成!如需咒语可私戳哦! 目录 前言需求日期格式代码日期类型数据对应正则表达式前言 要做数据分类分级,重要的是分类分级模版的合理性和数…

一致性 Hash 算法 及Java TreeMap 实现

1、一致性 Hash 算法原理 一致性 Hash 算法通过构建环状的 Hash 空间替线性 Hash 空间的方法解决了这个问题,整个 Hash 空间被构建成一个首位相接的环。 其具体的构造过程为: 先构造一个长度为 2^32 的一致性 Hash 环计算每个缓存服务器的 Hash 值&…

「C/C++」C++对已有的类进行扩充

博客主页:何曾参静谧的博客 文章专栏:「C/C」C/C学习 目录 相关术语一、 继承二、组合 相关术语 继承:继承父类后可以拥有父类对应的属性和方法。 组合:将类作为成员对象,基类可以直接调用派生类对应的属性和方法。 一…

MySQL_第08章_聚合函数

第08章_聚合函数 讲师:尚硅谷 - 宋红康(江湖人称:康师傅) 官网: http://www.atguigu.com 我们上一章讲到了 SQL 单行函数。实际上 SQL 函数还有一类,叫做聚合(或聚集、分组)函…

59 openEuler 22.03-LTS 搭建MySQL数据库服务器-软件介绍和配置环境

文章目录 59 openEuler 22.03-LTS 搭建MySQL数据库服务器-软件介绍和配置环境59.1 软件介绍59.2 配置环境59.2.1 关闭防火墙并取消开机自启动59.2.2 修改SELINUX为disabled59.2.3 创建组和用户59.2.4 创建数据盘59.2.4.1 方法一:在root权限下使用fdisk进行磁盘管理5…