数据结构(C):玩转链表

news2024/12/23 11:59:44

 

🍺0.前言

        言C之言,聊C之识,以C会友,共向远方。各位博友的各位你们好啊,这里是持续分享数据结构知识的小赵同学,今天要分享的数据结构知识是链表,在这一章,小赵将会向大家展开聊聊链表。✊

1.链表的概念

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

当然了光靠这个字面上的的理解就想理解链表我感觉还是蛮难的,所以呢,小赵在这里为大家找了一幅图片,方便大家理解 

有人说这不是一个火车吗?怎么会和链表扯上关系,但实际上链表和火车是极其相似的,我在这里为大家画了个链表的图。

 

链表的结构就是一环接着一环的,哪这每一个环是什么呢?其实就是我们的结构体了。我们将结构体的指针放在前一个结构体的里面,来实现彼此之间的互通,从而形成一个链表。

2.链表的分类 

可能看到这个标题有人会疑惑为什么链表还有分类呢?不就是一根链子吗?实际上则不然,相比较我们前面所说的火车,链表的可能性更多,它可以没有火车头,它可以彼此之间相互拉着,你拉着我,我拉着你,它们可以从头到尾,也可以围成一个圈,成一个环。

那么究竟到底有哪几种呢?小赵按照三个特征为他们划分,同时它们可以自由组合。

2.1带头不带头

带头和不带头长啥样呢就是下面这个样子

那带头和不带头有啥区别呢?其区别其实和火车很像,我们知道火车头往往是不载客的,而其实我们的这个头也是不载数据的,当然最主要的区别还是在我们后面使用的方便度上。

2.2单向和双向

其实我觉得单向和双向的例子还蛮好理解的,就像我们谈恋爱一样,你单喜欢她,她不喜欢你,就叫单向。互相喜欢就是双向。 

2.3循环和不循环

循环和不循环其实也相对比较好理解一点,在这里就不多说了。

这三个特征各位可以随意组合都能构成链表,同时各位用这三个基本特征去判断链表,也能让各位准确地判断出这是什么链表。

2.4主要使用的链表 

虽然链表的种类如此之多之杂,但实际上我们正常使用的链表只有下面两个。


说白了,就是一个是白手起家,一个是啥都有的大土豪。白手起家的可能在我们的刷题中是很常见的,因为毕竟是白手起家,难度可能会更大一些。而什么都有这个各位后面会知道真的很爽,但题大多数题目是不会给你这么爽的。所以今天我们聊链表的时候会用我们的穷小子,来做,这样以后做大土豪也更容易一些。

3.链表的实现 

 那么链表该如何实现呢?跟上面的顺序表一样,我们也是从链表的功能增删查改来玩。

3.1申请一个链表

这里我们换一种和之前的顺序表不一样的方式,我们直接用指针去玩,因为链表里面的指针非常多,我们可以直接申请一个指针的链表。

SListNode* BuySListNode(SLTDateType x)
{
	SListNode* a = (SListNode*)malloc(sizeof(SListNode));//创建一小节链表
	if(a==NULL)
	{
		perror("malloc failed");
		return;
	}
	a->data = x;
	a->next = NULL;
    return a;
}

3.2头插和尾插

 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{

	assert(*(pplist));
	SListNode* newnode = BuySListNode(x);//创建一个新节点
	newnode->next = *(pplist);//让新节点接上原链表的头节点
	*(pplist) = newnode;//改变原链表的头节点
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
	assert(*(pplist));
	SListNode* newnode = BuySListNode(x);
	SListNode* node = *(pplist);//记录原链表的头节点
	while (node->next)//找到原链表的尾节点
	{
		node = node->next;
	}
	node->next = newnode;//插入新的尾巴节点
}

好了,在这个代码中,我们也有一些常见的问题。比如小赵当时学的时候就挺奇怪为啥这里不能直接用我们的指针,而要找双指针呢?其实这个时候就是我们在函数那边的一个知识,或者说是指针那边的知识。

3.2.1函数的形参问题

为了方便大家更深入理解这个问题,小赵会带着大家重新回顾之前的知识,同时加深我们对这一块知识的理解。

首先拿我们最熟悉最经典的Swap函数聊起。

void Swap(int x, int y)
{
	int tmp = x;
    x = y;
	y = tmp;
}
int main()
{
	int a = 5;
	int b = 6;
	Swap(a, b);
	printf("a=%d b=%d" ,a,b);
}

 

按照大多数人的理解方式可能这里就很奇怪了,为啥呢?为啥没有交换呢?

实际上这是我们对函数的形参理解不够透彻导致的。其实你向函数传送一个实参的,那边的形参就会将你的函数里面的值给接受了,如果说的形象一点就是接力跑。如果我们把a的值想象成一个棒子的话,那么a向函数里面传参的过程实际上就是a在把棒子给形参的过程。后面跑的实际是形参,跟你这个实参一点关系都没有,你a就留在了原地。拿这里究竟该如何去改呢?就是用我们的指针的知识。因为指针所携带的是你的地址,他可以通过地址找到你从而改变你。

 我们发现这个过程中我们传输东西的本质其实没有变,我们还是将接力棒给了形参,但是这次的形参里面有我们的地址,那么他进行解地址的时候,就可以访问到我们的实参了,从而实现交换的效果。

3.2.2二级指针问题解决

而我们这里为何会使用起二级指针呢?其实原因跟上面一样,我们发现只有把我们实参的地址传过去才能真正改变我们的实参.那要改变我们的指针,就要把我们指针的地址传过去,可我们发现一个问题,那就是普通的地址,指针是能够接受的。那指针的地址呢?用我们前面的知识我们知道,只有二级指针才能接收一级指针的地址,所以这里我们用了二级指针。如果你还是不太清楚整个的逻辑的关系,没关系,小赵还给各位画了一个图。

 相信有了上面的知识各位再看这个代码就会轻松多了,如果各位还有什么问题,也可以私信小赵哦。

 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);//创建一个新节点
	newnode->next = *(pplist);//让新节点接上原链表的头节点
	*(pplist) = newnode;//改变原链表的头节点
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);
	SListNode* node = *(pplist);//记录原链表的头节点
	while (node->next)//找到原链表的尾节点
	{
		node = node->next;
	}
	node->next = newnode;//插入新的尾巴节点
}

3.3头删和尾删

 单链表头删
void SListPopFront(SListNode** pplist)
{
	assert(*(pplist));//防止没有节点
	SListNode* node = *(pplist);//记录原链表的头节点
	*(pplist) = node->next;
	free(node);//释放掉原头节点
	node = NULL;
}
//单链表的尾删
void SListPopBack(SListNode** pplist)
{
	assert(*pplist);
	SListNode* node = *(pplist);//记录原链表的头节点
	while (node->next && node->next->next)//找到原链表的尾节点的前一个节点
	{
		node = node->next;
	}
	SListNode* end = node->next;//保存尾节点
	node->next = NULL;
	free(end);//释放尾节点
	end = NULL;
}

3.4打印链表

void SListPrint(SListNode* plist)
{
    assert(plist);
	while (plist)
	{
		printf("%d->", plist->data);
		plist = plist->next;
	}
}

3.5查找

SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	assert(plist);
	while (plist)
	{
		if (plist->data == x)
		{
			return plist;
		}
		plist = plist->next;
	}
	return NULL;
}

3.5销毁链表

void SLTDestroy(SListNode** pphead)
{
	SListNode* node = *pphead;
	while (node)//直到为空指针为止
   {
		SListNode* node2 = node;//保留当前指针
		node = node->next;//到下一个节点
		free(node2);//释放之前的那个
		node2 = NULL;
	}
	node = NULL;
}

3.6某个位置插入和删除  

3.6.1前插

void SLTInsert(SListNode** pphead, SListNode* pos, SLTDateType x)
{
	assert(pos);
	SListNode* node = *pphead;
	SListNode* newnode = BuySListNode(x);
	if (pos == *pphead)//如果是头节点特殊处理
	{
		newnode->next = node;
		*pphead = newnode;
		return;
	}
	while ( node->next != pos)//找到pos前一个节点
	{
		node = node->next;
	}
	SListNode* begin = node;//要插入的节点的前一个节点
	SListNode* end =pos;//要插入的节点的后一个节点
	begin->next = newnode;
	newnode->next = end;
}

前插的难度是要比后插大的,其原因就在于前插要考虑可能是头插的情况发生,同时 前插还要通过while去找到前一个节点,这一点导致了其的难度大。

3.6.1后插

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
    assert(pos);
	SListNode* end = pos->next;//保留下一个节点
	SListNode* node = BuySListNode(x);//创建新节点
	pos->next = node;//重新连接
	node->next = end;
}

这个是在我们某一个节点后插入,其的感觉像是什么呢?就像是小赵下面这个画一样。

 

断开原连线,连上新的线。

 3.6.3前删

void SLTErase(SListNode** pphead, SListNode* pos)
{
	assert(pos);
    if (pos == *pphead) return;
	SListNode* node = *pphead;
	if (pos == (*pphead)->next )//如果是头删,特殊处理
	{
		free(node);
		node = NULL;
		*pphead = pos;
		return;
	}
	while (node->next->next  != pos)
	{
		node = node->next;
	}
	SListNode* node2 = node->next ;//找到被删节点的前一个节点
	node->next = pos;
	free(node2);
	node2 = NULL;
}

3.6.4后删

void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	if (pos->next == NULL) return;//处理特殊情况
	SListNode* end = pos->next->next;//保存后面的后面的节点
	SListNode* node = pos->next;
	pos->next = end;
	free(node);
	node = NULL;
}

 4.结束语

好了小赵今天的分享就到这里了,如果大家有什么不明白的地方可以在小赵的下方留言哦,同时如果小赵的博客中有什么地方不对也希望得到大家的指点,谢谢各位家人们的支持。你们的支持是小赵创作的动力,加油。

如果觉得文章对你有帮助的话,还请点赞,关注,收藏支持小赵,如有不足还请指点,小赵及时改正,感谢大家支持!!!

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

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

相关文章

c语言排序算法之八(桶排序)

前言 以下内容是被验证可以有效理解桶排序,代码也较容易理解。如果你发现还有很多需要增加的,欢迎留言。 为什么要单独写排序算法这一系列,看过一些贴子普遍篇幅较长。看完依旧难以直观理解原理及整个过程。代码永远是基于理解的基础上才能…

Carla基础 | Carla预编译版安装与ROS联合仿真图文教程

目录 1 什么是Carla?2 Carla预编译版安装2.1 独立显卡配置2.2 安装ROS2.3 启动虚拟环境2.4 安装Carla预编译版2.5 安装carla-ros-bridge 3 测试案例常见问题 1 什么是Carla? Carla是由西班牙巴塞罗那自治大学计算机视觉中心指导开发的开源仿真模拟器&…

Redis-五大数据类型-Set(集合)

五大数据类型-Set(集合) 简介 与List类似是一个列表功能,但Set是自动排重的,当需要存储一个列表数据,又不希望出现重复数据时,Set是一个很好的选择。 Set是String类型的无序集合,它底层其实是…

学生宿舍智能电表系统改造升级意义

石家庄光大远通电气有限公司学生宿舍智能电表控制系统改造升级功能与意义** 一、实时监测 宿舍智能电表控制系统具备实时监测功能,能够实时收集、记录和分析每个宿舍的用电数据。这种监测不仅可以帮助管理者掌握用电情况,还可以为用户提供详细的用电报…

重生奇迹mu魔剑士怎么转职

重生奇迹MU中的魔剑士怎么才可以转职? 随便建个角色升级到220级,然后小退重建就有魔剑士了。另外魔剑不用二转。400的三转和其他职业一样。 战士不能转魔剑的。当帐号中已经拥有一位等级超过220级以上的角色时,才可以创造职业为魔剑士的新角色。 魔剑…

gcc编译器分析

gcc编译器分析 参考词法分析语法分析预读一个符号语法分析函数调用关系重点函数分析c_parse_filec_parser_translation_unit 参考 《gcc源码分析》 词法分析 词法分析的过程就是将源代码识别成一个一个的词法符号,并在词法分析的过程中创建一些树节点&#xff0c…

YashanDB与帆软信创商业智能软件完成兼容互认证

近日,深圳计算科学研究院崖山数据库系统YashanDB与帆软信创商业智能软件(V6.0)顺利完成兼容性互认证,经严格测试,双方产品能够相互兼容,稳定运行。 崖山数据库系统YashanDB是深圳计算科学研究院自主研发设计…

一分钱不花从HTTP升级到HTTPS

HTTP升级到HTTPS是一个涉及安全性和技术实施的过程,主要目的是为了提升网站数据传输的安全性,防止数据被窃取或篡改。以下是一些关于从HTTP升级到HTTPS的技术性要点和步骤概述,结合上述信息资源: 一、理解HTTPS的重要性 HTTPS (…

[图解]SysML和EA建模住宅安全系统-01

1 00:00:00,980 --> 00:00:03,100 接下来,我们来看一下案例 2 00:00:04,930 --> 00:00:06,750 我们这次课程的案例 3 00:00:07,090 --> 00:00:13,800 选用了SysML实用指南的书上 4 00:00:13,810 --> 00:00:16,180 第十七章这个案例 5 00:00:16,350 …

《QT实用小工具·五十六》自适应界面变化的控件

1、概述 源码放在文章末尾 该项目实现了网格显示多张带文字的图片在界面中自适应布局 特点 跟随窗口大小变换位置,并带移动动画 响应鼠标事件,图片缩放动画 点击水波纹动画 项目demo演示如下所示: 项目部分代码如下所示: #i…

​可视化大屏C位图:3D模型,可视化大屏的画龙点睛之处

Hello,我是大千UI工场,本期可视化大屏的焦点图(C位)分享将图表作为焦点图的情形,欢迎友友们关注、评论,如果有订单可私信。 3D模型在可视化大屏中有很大的价值,以下是一些相关的优点&#xff1a…

优优嗨聚集团:法律明灯,个债处理中的法律咨询力量

在现代社会,个人债务问题日益突出,无论是因生活消费、投资失利还是其他原因,债务问题都可能成为个人财务的一大负担。面对复杂的债务困境,许多人感到迷茫和无助。此时,法律咨询如同一盏明灯,能够为个人债务…

GEE数据集——全球冰川海拔变化产品(2000-2019 年)

全球冰川海拔变化产品(2000-2019 年) 该数据集提供了 2000 年至 2019 年期间冰川海拔和质量变化的全面且全球一致的记录。它利用大量卫星图像(主要来自美国国家航空航天局(NASA)的高级星载热发射和反射辐射计&#xf…

ABAP开发(1)事物代码

文章目录 1、查看系统部分功能的T-code2、T-code使用3、查看未知程序的T-code4、常用T-code 在SAPGUI中,事务代码(transaction code),简称T-code,是一个特定的编号,支持自定义,使用户能够快速访…

【AI+音视频总结】如何在几分钟内用智能工具摘取音视频精华?揭秘下一代学习和内容创作神器!

今天无意发现一个网站,可以一步到位完成AI音视频总结。 我之前对于音视频总结的步骤还是借助 工具下载 剪映来完成的。详情可以参考之前写的一篇文章 【AI应用】模仿爆款视频二次创作短视频操作步骤 。 这里介绍的网站是 BibiGPT 。 BibiGPT AI 音视频助理 - 它是…

项目管理-项目沟通管理

项目管理:每天进步一点点~ 活到老,学到老 ヾ(◍∇◍)ノ゙ 何时学习都不晚,加油 1.项目沟通管理-主要内容 项目沟通管理过程--重点: ①ITTO 输入,输出工具和技术。 ②问题和解决方案。 ③论文…

公共代理IP和独享代理IP之间的区别?

公共代理IP和独享代理IP在网络应用中扮演着不同的角色,它们之间的区别主要体现在使用方式、性能、安全性以及隐私保护等方面。以下是对这两种代理IP的详细对比和分析。 第一点就是使用的方式以及成本上的不同,公共代理IP,顾名思义&#xff0…

同向双指针(滑动窗口)算法

209. 长度最小的子数组 这里的更新结果就题来定 class Solution {public int minSubArrayLen(int target, int[] nums) {int sum 0;int len 0;int f 0;for(int left 0, right 0; right < nums.length;){//求和sum nums[right];while(sum > target){//lenint t ri…

浪漫编码:手把手教你实现校园表白墙功能

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;浪漫编码&#xff1a;手把手教你实现校园表白墙功能 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 这里写目录标题 表白墙数据准备引入MyBatis和MySQL驱动依赖…

部分设计模式概述

单例模式 工厂模式 适配器模式 模板方法模式 策略模式 责任链 观察者模式&#xff08;又叫发布订阅模式&#xff09;