【C语言】数据结构-单链表

news2024/11/26 23:48:44

主页:114514的代码大冒险

qq:2188956112(欢迎小伙伴呀hi✿(。◕ᴗ◕。)✿ )

Gitee:庄嘉豪 (zhuang-jiahaoxxx) - Gitee.com

文章目录

目录

文章目录

前言(链表的优势)

一、单链表是什么

二、单链表操作的具体代码实现

1.准备工作

2.打印链表

2.尾插(在链表末端添加数据)

3、头插(在链表首端添加数据)

4,创建新节点

5,尾删(在链表末端删除数据)

6,头删(在链表首部删除数据)

7,寻找目标数据

8,插入(在目标位置的前面插入)

9.插入(在目标位置的后面插入)

10.指定删除

 11,销毁链表(释放内存)

总结



 

前言(链表的优势)

同样是线性存储结构(能用线串起来的结构),顺序表是连续的,而链表是离散的,

顺序表的内存申请是有多余消耗的,而链表是没有的(按需索取),

顺序表在插入与删除数据上的时间消耗是巨大的,而链表则是简易的

一、单链表是什么

单链表:一种区别于顺序表的线性离散存储结构,节点的基本构成为数据和下一节点的地址,

首尾不相连,首节点没有前驱,尾节点没有后继,

概念图:

 上图的箭头在实际中是不存在的,这只是为了方便我们去理解

物理图:

 这里我们稍微分析一下这个结构,方便后续内容的理解,

图中的plist的意思是表头,它仅仅存储链表首节点的位置,于是我们就可以通过它得知首节点的地址,首节点存储了数据“1”和下一个节点的地址“0x0012FFB0”,第二第三节点以此类推

而尾节点的存储地址的位置存储了空指针,意味着链表的结束,这一个个节点地址的存储,使他们相互联系。

二、单链表操作的具体代码实现

1.准备工作

首先,依旧按照标准工程的标准,建立三个文件如下图所示:
这里的“SLsit.h”  存放工程所用头文件以及接口函数的声明        “ SList.c” 调用接口进行功能测试    “Test.c ”进行接口的实现

 然后就是节点的实现(存储数据和下一节点的地址):

typedef int SLTDateType;

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

我们稍微分析一下,首先将数据类型定义成 SLTDateType 以至于可以以后便捷的改变数据类型

我们依旧是将这部分代码放于头文件中,

然后就是节点结构体的的创建,data存放数据,next被定义为与节点相同类型的指针(方便存储下一个节点的地址),用typedef将结构体的名字简化为:SLTNode

2.打印链表

我们先来点容易的,适应一下链表的结构:

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

	}

	printf("NULL\n");
}

函数传参(Test.c 文件中):

 这里的plist与phead指向同一个意思

这里需要好好说明的一点为“cur = cur->next”,我们直接上图,

 这个就是一个顺着头节点地址向下延伸的过程,这个图中cur与cur->next都分别代表着哪个地址

我也用了下划线(红)标明,相信同志们一定能看懂

2.尾插(在链表末端添加数据)

先在申请一节点大小的动态内存,然后分链表为空(头指针指向空指针)和链表不为空两种情况

我们下面来看一段代码:

void SListPushBack(SLTNode* phead, SLTDateType x)//尾插
{

	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	newnode->next = NULL;

	if (phead == NULL)
	{
		phead = newnode;
	}
	SLTNode* tail = phead;
	while (tail->next != NULL)
	{
		tail = tail->next;
	}



	tail->next = newnode;
}

 这个就是我们的一个大概思维实现,emmmm,但是这个接口其实是有问题的

这里我们要提到形参的改变不影响实参

 对照上文接口,我们要知道的是, 如果我们希望函数改变我们所传变量,那么就需要传它的地址

这里我们想改变的是头地址,它本身就是个地址,所以我们传参时,要传地址的地址

 

具体正确代码

如下图所示:

void SListPushBack(SLTNode** pphead, SLTDateType x)//尾插
{

	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	newnode->next = NULL;//新节点的创建

	
    //分情况
	if (pphead == NULL)
	{
		*pphead = newnode;
	}
	SLTNode* tail = *pphead;
	while (tail->next != NULL)
	{
		tail = tail->next;
	}


	tail->next = newnode;
}

3、头插(在链表首端添加数据)

代码:

void SListPushFront(SLTNode** pphead, SLTDateType x)
{
	SLTNode* newnode = BuyListNode(x);//这是个创建新节点的函数
	//为空指针时解释一下
	newnode->next = *pphead;
	*pphead = newnode;

}

解释:

4,创建新节点

SLTNode* BuyListNode(SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	//一兆合一百万字节
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

这里因为插入数据都需要新节点,所以,我们将这一功能独立出来,这里没什么特别的,我们接着往下走。

5,尾删(在链表末端删除数据)

代码:

void SListPopBack(SLTNode** pphead, SLTDateType x)
{
	//第一种办法
	//SLTNode* prev = NULL;

	//SLTNode* tail = *pphead;

	//while (tail->next)
	//{ 
	//	prev = tail;
	//	tail = tail->next;
	//}

	//free(tail);
	//tail = NULL;

	//prev->next = NULL;

	//第二种办法
	SLTNode* tail = *pphead;
	while (tail->next->next)
	{
		tail = tail->next;
	}
	free(tail->next);
	tail->next = NULL;


}

两种办法,思路差不多,第一种办法prev代表上一节点,而第二种办法则省去了这种变量的创建

为什么要保留上一节点:

说道尾删,我们很容易想到就是把尾节点的内容置为空,但是这个时候倒数第二个节点的next

仍然指向尾节点,而尾节点已不存在,于是next就成了野指针,所以我们要保留倒数第二个节点,

将其next置为空

但是,这个代码仍然不是完善的,它没有考虑空链表和只用一个节点的链表(需单独处理)

以下为正确代码:

void SListPopBack(SLTNode** pphead, SLTDateType x)
{


	if (*pphead == NULL)
	{
		return;
	}

	//assert(*pphead != NULL);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;

	}
	else
	{
		
		SLTNode* prev = NULL;

		SLTNode* tail = *pphead;

		while (tail->next)
		{ 
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;

		prev->next = NULL;
	}


	//第二种办法
	SLTNode* tail = *pphead;
	while (tail->next->next)
	{
		tail = tail->next;
	}
	free(tail->next);
	tail->next = NULL;


}

6,头删(在链表首部删除数据)

代码:

void SListPopFront(SLTNode** pphead)
{

	assert(*pphead != NULL);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

图解:

注意:

这里仍然要考虑链表为空的情况

处理办法有assert警告和if语句限制

//assert(*pphead != NULL);
	if ((*pphead)->next == NULL)

7,寻找目标数据

代码:

SLTNode* SListFind(SLTNode* phead, SLTDateType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}

	return NULL;
}

解释:

找到就返回地址,没找到就用“cur = cur->next”进行推进,直到走到链尾的空指针

8,插入(在目标位置的前面插入)

//在pos前面插入
//单链表不适合在pos前面插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	SLTNode* newnode = BuyListNode(x);
	// 找到pos的前一个位置
	if (*pphead == pos)
	{
		newnode->next = *pphead;
		*pphead = newnode;
	}
	else
	{
		SLTNode* posPrev = *pphead;

		while (posPrev->next != pos)
		{
			posPrev = posPrev->next;
		}

		posPrev->next = newnode;
		newnode->next = pos;
	}

}

解释:

分为首节点即目标,和其他情况

但都是在找pos的前一个位置

找到后进行插入

图:

9.插入(在目标位置的后面插入)

 代码:

void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	SLTNode* newnode = BuyListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

图解:

这个就比在pos前面插入要来的简单,时间复杂度更是从O(N)降到了O(1) 

10.指定删除

删除指定位置的数据:

void SListEase(SLTNode** pphead, SLTNode* pos)
{
	if (*pphead == pos)
	{
		/**pphead = pos->next;
		free(pos);*/
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
	
}

思路:

找到指定位置的前一个节点,然后进行删除操作,承继上文中的思想、、


删除指定节点的后一个节点

代码实现:        

void SListEaseAfter(SLTNode* pos)
{
	SLTNode* next = pos->next;
	pos->next = next->next;//前一个next指的是节点,后一个next是节点内存储下一个节点的地址
	free(next); 

}

 11,销毁链表(释放内存)

代码;

void SListDestory(SLTNode** pphead)
{
	assert(pphead);

	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

思路:

 用cur保存当前位置,用next保存下一个位置 ,并利用它向下一个位置推进

而cur则用于 进行当前节点的内存释放


总结

这就是本次单链表的全部内容了,接下来就是双链表的学习,结束之后我们就正式进入数据结构初阶的内容学习了,这段内容还是要多多做题,多多画图,多多思考,不怕浪费时间的,我在单链表

上卡了很长一段时间,卡到怀疑自己是否应该放弃,

我也是看了很多老师的,很多个版本才慢慢有些理解这到底是怎么回事,当我明白时,真的犹如拨云见日,hhh, 当时真的是高兴,所以想着好好写一写,

希望能够帮助到和我同样陷入困境的小伙伴,hhh,如果这次的文章能够帮到你,

便是某人莫大的福分啦,冲冲冲

花在这一部分内容的时间会成为我们在IT行业扎根的巨大支撑。,冲啊!!

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

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

相关文章

Hadoop基础之《(2)—Hadoop概述》

一、Hadoop是什么 1、Hadoop是一个由Apache基金会所开发的分布式系统基础架构。 2、主要解决,海量数据的存储和海量数据的分析计算问题。 3、广义上来说,Hadoop通常是指一个更广泛的概念—Hadoop生态圈。 二、Hadoop的三大发行版本 Apache版本&#x…

【论文简述】Long-range Attention Network for Multi-View Stereo(WACV 2021)

一、论文简述 1. 第一作者:Xudong Zhang 2. 发表年份:2021 3. 发表期刊:WACV 4. 关键词:MVS、注意力、级联、监督回归 5. 探索动机:先前的方法忽略了像素之间的依赖关系,并且期望回归的方式效率不高。…

Kettle(10):switch/case组件

1 机智的体育老师——条件判断 有一天,体育老师要让班上的男女同学分别排成两队。但这个班上还有几名同学,很特殊——他们是蜘蛛!!所以,机智的体育老师需要把他们排成三队,男同学一队,女同学一队,蜘蛛一队。 体育老师要做一件非常重要的事情:判断学生是男孩还是女孩、…

联想E14 开机黑屏报错2102:Detection error on HDD0 (Main HDD)

环境: 联想E14 Win10专业版 问题描述: 联想E14 开机黑屏报错2102:Detection error on HDD0 (Main HDD) 解决方案: 1.关机重启 2.尝试拆解,拔出硬盘数据线,重新安装

Scanpy 单细胞基因分析

参考:https://www.bilibili.com/video/BV1sq4y1C7Qx/ https://scanpy-tutorials.readthedocs.io/en/latest/pbmc3k.html 代码下载:scanpy分析scRNA-seq数据基本流程(含scanpy seurat两大工具对比) 链接: https://pan.baidu.com/s…

华为机试 HJ33 整数与IP地址间的转换

华为机试题 HJ33 整数与IP地址间的转换 一、题目描述 描述原理:ip地址的每段可以看成是一个0-255的整数,把每段拆分成一个二进制形式组合起来,然后把这个二进制数转变成一个长整数。举例:一个ip地址为10.0.3.193每段数字 …

C# - JSON Schema validation

C# - JSON Schema validation引言如何生成 C# 类 JSON Schema利用在线工具利用 Visual Studio利用 NJsonSchema验证 JSON Schema针对 JSON Schema 字符串针对 C# 类 Schema引言 针对 API 测试,我们需要验证 Response 中的一些字段类型,是否缺省等&#…

Linux操作系统精讲之高级IO

代码在: https://github.com/sjmshsh/System-Call-Learn 通过阅读本篇文章,你可以收获: 理解五种IO模型的基本概念,重点是IO多路转接掌握select,poll,epoll系统调用接口并实现简易的TCP服务器理解epoll的…

10、创建不同类型的工程

文章目录10、创建不同类型的模块10.1 创建Java模块10.2 创建Java Web模块1 IDEA中配置Tomcat2 创建Web工程3 配置Web工程并运行4 乱码的解决10.3 创建Maven Java模块1 Maven的介绍2 Maven的配置3 Maven Java工程的创建4 编写代码及测试10.4 创建Maven Web模块1 创建Maven的Web模…

java基础语法——断点调试与数据加密(基础语法练习学习)

目录 Eclipse的断点调试 基础语法的练习 Eclipse的断点调试 作用:查看程序执行流程和调试程序 断点: 就是一个标记,就是我们经常用到的debug(检查程序错误,我们用到的是debug as) A–哪里加?—— 在实际的程序行号…

Knowledge-based-BERT(二)

多种预训练任务解决NLP处理SMILES的多种弊端,代码:Knowledge-based-BERT,原文:Knowledge-based BERT: a method to extract molecular features like computational chemists,代码解析继续K_BERT_WCL_pretrain。模型框…

Java多线程 - 线程安全和线程同步解决线程安全问题

文章目录线程安全问题线程同步方式一: 同步代码块方式二: 同步方法方式三: Lock锁线程安全问题 线程安全问题指的是: 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。 举例: 取钱模型演示 需求:小明和小红是一对夫妻&am…

建单向链表-C语言实现

任务描述 本关需要你建立一个带头结点的单向链表。 相关知识 什么是链表?链表和二叉树是C语言数据结构的基础和核心。 链表有多种形式,它可以是单链接的或者双链接的,可以是已排序的或未排序的,可以是循环的或非循环的。 本关让我们来学习单链表。 单链表 单向链表(单…

XC-16 SpringSecurity Oauth2 JWT

SpringSecurityOauth2用户认证需求分析用户认证与授权单点登录需求第三方认证需求用户认证技术方案单点登录技术方案Oauth2认证Oauth2认证流程2.2.2Oauth2在本项目中的应用SpringSecurity Oauth2认证解决方案SpringSecurityOauth2研目标搭建认证服务器导入基础工程创建数据库Oa…

一起自学SLAM算法:9.2 LSD-SLAM算法

连载文章,长期更新,欢迎关注: 下面将从原理分析、源码解读和安装与运行这3个方面展开讲解LSD-SLAM算法。 9.2.1 LSD-SLAM原理分析 前面已经说过,LSD-SLAM算法是直接法的典型代表。因此在下面的分析中,首先介绍一下直…

学习笔记:Java 并发编程④

若文章内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系博主删除。 视频链接:https://www.bilibili.com/video/av81461839配套资料:https://pan.baidu.com/s/1lSDty6-hzCWTXFYuqThRPw&am…

CSS语法格式与三种引入方式

文章目录第一章——CSS简介1.1 CSS语法格式1.2 CSS 位置1.3 CSS引入方式1.3.1.行内样式表(内联样式表)1.3.2 外部样式表1.3.3 内部样式表第一章——CSS简介 1.1 CSS语法格式 CSS 规则由两个主要的部分构成:选择器以及一条或多条声明。 选择…

C语言全局变量和局部变量

局部变量定义在函数内部的变量称为局部变量(Local Variable),它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。例如:intf1(int a){ int b,c;//a,b,c仅在函数f1()内有效 return abc; } i…

各种CV领域 Attention (原理+代码大全)

人类在处理信息时,天然会过滤掉不太关注的信息,着重于感兴趣信息,于是将这种处理信息的机制称为注意力机制。 注意力机制分类:软注意力机制(全局注意)、硬注意力机制(局部注意)、和…

打工人必知必会(三)——经济补偿金和赔偿金的那些事

目录 参考 一、经济补偿金&赔偿金-用人单位承担赔偿责任 1、月平均工资是税前还是税后工资? 3、经济补偿金是否要交个人所得税?如何交? 二、劳动者承担赔偿责任 三、劳动者需要特别注意 参考 《HR全程法律顾问:企业人力资…