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

 

目录

🍺0.前言

1.链表的概念

2.链表的分类 

2.1带头不带头

2.2单向和双向

2.3循环和不循环

2.4主要使用的链表 

3.链表的实现 

3.1申请一个链表

3.2头插和尾插

3.2.1函数的形参问题

3.2.2二级指针问题解决

3.3头删和尾删

3.4打印链表

3.5查找

3.5销毁链表

3.6某个位置插入和删除  

3.6.1前插

3.6.1后插

 3.6.3前删

3.6.4后删

 4.结束语


🍺0.前言

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

1.链表的概念

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

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

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

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

2.链表的分类 

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

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

2.1带头不带头

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

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

2.2单向和双向

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

2.3循环和不循环

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

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

2.4主要使用的链表 

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


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

3.链表的实现 

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

当然开始之前我们肯定是要先定义一个节点:

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

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.mzph.cn/bicheng/10831.shtml

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

相关文章

【谷粒商城】03创建商品模块

1.创建模块 2.创建项目微服务 商品服务、仓储服务、订单服务、优惠券服务、用户服务 共同: 1)、web、openfeign 2)、每一个服务,包名 com.atguigu.gulimall.xxx(product/order/ware/coupon/member) 3)、模块名&#x…

​《MATLAB科研绘图与学术图表绘制从入门到精通》示例:绘制德国每日风能和太阳能产量3D线图

在MATLAB中,要绘制3D线图,可以使用 plot3 函数。 在《MATLAB科研绘图与学术图表绘制从入门到精通》书中通过绘制德国每日风能和太阳能产量3D线图解释了如何在MATLAB中绘制3D线图。 购书地址:https://item.jd.com/14102657.html

完美解决Windows10下-更换JDK环境变量后,在cmd下执行仍java -version然出现原来版本的JDK的问题

一、错误场景预演 本人欲将 JDK 1.8 通过安装包的方式升级为 JDK 22。 本地旧版本:1.8.0_221预升级版本:22.0.1 1.1、查看本地旧版本 在配置环境变量之前,首先我们要明确,本地存在旧版本,如果本地没有 Java&#x…

MFC通过继承现有控件自定义控件

在MFC 自定义控件,可以通过继承MFC提供的控件类(如CButton、CEdit、CListBox等)并重写其成员函数和消息处理函数来实现。 以下是一个基本的步骤指南,用于在MFC中创建自定义控件: 确定要继承的基类: 首先…

vm16安装最新版本的ubuntu虚拟机,并安装g++的步骤记录

背景 低版本的ubuntu安装G一直不成功,干脆安装最新版的 官网下载 bing搜索ubuntu 下载完成 vm16新建虚拟机 一直下一步,安装完成 终端输入命令 sudo apt-get update ᅟᅠ       sudo apt install gcc ᅟᅠ      sudo apt install g

树莓派点亮FPGA小灯

树莓派点亮FPGA小灯 引言: ​ 本次实验的目的是通过树莓派和FPGA之间的串口通信,控制FPGA开发板上的小灯。实验将展示如何使用树莓派发送特定的字符信号,通过串口传输至FPGA,并在FPGA上实现逻辑解析,以点亮指定的小灯。…

【QT】QT背景介绍

本专栏内容为:QT学习专栏 通过本专栏的深入学习,你可以了解并掌握QT。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:QT 🚚代码仓库:小小unicorn的代码仓库🚚 🌹&#x1f…

3D分子生成的定制扩散框架 MolDiff - 评测

MolDiff模型是一种考虑分子键生成的3D分子生成的新模型。MolDiff是清华大学智能产业研究院马剑竹课题组发表在PMLR 2023的工作,第一作者是Xingang Peng,文章题目为:《 Addressing the Atom-Bond Inconsistency Problem in 3D Molecule Genera…

【Android】Kotlin学习之数据容器 -- 集合

一. 定义 List : 是一个有序列表, 可通过下标访问元素. 元素可以在list中出现多次, 元素可重复 Set : 是元素唯一的集合, 一般来说Set中元素的顺序并不重要, 无序集合. Map : 是一组键值对, 键是唯一的, 每个键刚好映射到一个值, 值可以重复 二. 集合创建 三. 示例 mutabl…

OSTE-Web-Log-Analyzer:基于Python的Web服务器日志自动化分析工具

关于OSTE-Web-Log-Analyzer OSTE-Web-Log-Analyzer是一款功能强大的Web服务器日志自动化分析工具,该工具专为安全研究人员设计,能够使用Python Web日志分析工具(Python Web Log Analyzer)帮助广大研究人员以自动化的形式实现Web服…

推导 模型矩阵的逆转置矩阵求运动物体的法向量

一个物体表面的法向量如何随着物体的坐标变换而改变,取决于变换的类型。使用逆转置矩阵,可以安全地解决该问题,而无须陷入过度复杂的计算中。 法向量变化规律 平移变换不会改变法向量,因为平移不会改变物体的方向。 旋转变换会改…

栈和队列的相互实现

1. 两个队列实现栈. - 力扣(LeetCode) 队列的特点是先进先出,而栈的特点是后进先出(先进后出),也就是说重点在于利用两个队列来改变“出”的顺序。 假设我们在进行入栈操作的时候将数据依次入到一个队列中…

python如何单步调试

Python怎么单步调试?下面给大家介绍一下单步调试: 方法一:执行 python -m pdb myscript.py (Pdb) 会自己主动停在第一行。等待调试,这时你能够看看帮助。 方法二:在所调试程序的开头中:import pdb 并在你…

民航电子数据库:数据库的备份与恢复

目录 前言备份库级逻辑备份示例 恢复库级的逻辑恢复示例 前言 民航电子数据库的备份与恢复 备份 库级逻辑备份 备份目标库下所有的对象 。 因此 ,库级逻辑备份需要由备份库的管理员(SYSDBA)登录至备份目标库进行操作。 语法格式 &#xff1…

商家转账到零钱怎么开通?一步步教你玩转微信营销新利器

在数字化营销日新月异的今天,微信支付凭借其便捷、安全的特点,成为了商家不可或缺的支付工具。而其中的“商家转账到零钱”功能,更是为商家提供了一个全新的营销利器。今天,我们就来详细解读一下如何开通这一功能(我处…

怎么制作流程图?介绍制作方法

怎么制作流程图?在日常生活和工作中,流程图已经成为我们不可或缺的工具。无论是项目规划、流程优化,还是学习理解复杂系统,流程图都能帮助我们更直观地理解和表达信息。然而,很多人可能并不清楚,其实制作流…

通过 Java 操作 redis -- zset 有序集合基本命令

目录 使用命令 zadd,zrange 使用命令 zcard 使用命令 zrem 使用命令 zscore 使用命令 zrank 关于 redis zset 有序集合类型的相关命令推荐看Redis - Zset 有序集合 要想通过 Java 操作 redis,首先要连接上 redis 服务器,推荐看通过 Jav…

探索智慧推理:线上剧本杀小程序引领新潮流

随着科技的飞速发展,线上剧本杀小程序作为一种新兴的数字娱乐形式,正以其独特的魅力引领着新潮流,并在内容创造上展现出无限的潜力。这种融合了角色扮演、推理解谜和社交互动的游戏模式,不仅为用户带来了沉浸式的体验,…

物流单打印机怎么调格式距离,佳易王物流托运单管理系统软件打印单据左边距调节教程

物流单打印机怎么调格式距离,佳易王物流托运单管理系统软件打印单据左边距调节教程 一、前言 以下软件操作教程以,佳易王物流单打印管理软件为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、佳易王物流单管理系统打印…

Whistle Web Debugging Proxy介绍及使用

大家好,今天继续给大家分享一款抓包工具,这款抓包工具是网页的形式,方便多人访问同时维护。Whistle Web Debugging Proxy是一个用于HTTP、HTTPS、WebSocket等网络协议的跨平台调试工具。它可以帮助开发者对网络请求进行捕捉、分析、修改和重定…