【数据结构】单链表专题详细分析

与其临渊羡鱼,不如退而结网。💓💓💓

目录

•✨说在前面

🍋知识点一:什么是链表?

 • 🌰1.链表的概念 

 • 🌰2.链表的结构

 • 🌰3.链表的分类

🍋知识点二:单链表

 • 🌰1.顺序表的劣势

 • 🌰2.单链表动态申请节点

 • 🌰3.链表元素的打印

 • 🌰4.单链表头部插入元素

 • 🌰5.单链表尾部插入元素

 • 🌰6.指定位置之前插入数据

 • 🌰7.指定位置之后插入数据

 • 🌰8.单链表头部删除元素

 • 🌰9.单链表尾部删除元素

 • 🌰10.删除指定位置的节点

 • 🌰11.删除指定位置之后的节点

 • 🌰12.单链表的查找

 • 🌰13.单链表的销毁

🍋知识点三:单链表基本操作

• ✨SumUp结语


•✨说在前面

亲爱的读者们大家好!💖💖💖,我们又见面了,在之前的学习中我们了解了什么是数据结构以及顺序表的基本操作,而且还用顺序表做了通讯录的小项目。然而,顺序表并不是完美的,它的操作容易浪费空间,甚至降低程序的运行效率。我们今天要学习的链表,在这些地方就优于顺序表。

  

如果你没有准备好的话,或者说你还没有办法独立完成顺序表功能的代码,希望你先回去看看顺序表部分的内容,确认自己没有问题之后再来看这篇文章。

   

👇👇👇
💘💘💘知识连线时刻(直接点击即可)

  🎉🎉🎉复习回顾🎉🎉🎉

        【数据结构】顺序表专题详解(带图解析)

        【数据结构】基于顺序表实现通讯录

    

  博主主页传送门:愿天垂怜的博客

🍋知识点一:什么是链表?

 • 🌰1.链表的概念 

定义通讯录顺序表链表是一种线性数据结构,由一系列的节点组成,每个节点都包含数据和指向下一个节点的指针。

链表的元素在内存中不必连续排列,而是通过指针相互连接。

 链表的结构和火车箱相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节,只需要将火车的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。

设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下如何从车头走到车尾?

最简单的做法:每节车厢里都放一把下一节车厢的钥匙。

在链表里,每节"车厢"是什么样的呢?

 与顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,我们称之为"结点/节点"。节点的组成主要有两个部分:当前节点要保存的数据和保存下一个节点的地址(指针变量)。

图中指针变量 plist 保存的是第一个节点的地址,我们称 plist 此时"指向"第一个节点,如果希望 plist  "指向"第二个节点时,只需要修改 plist 保存的内容为 0x0012FFA0。

📌为什么还需要指针变量来保存下一个节点的位置?

链表中每个节点都是独立申请的(即需要插入数据时才申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点。

 • 🌰2.链表的结构

链表的基本结构由节点组成,每个节点包含数据和执行下一个节点的指针。

结合之前结构体的知识,我们可以写出节点数据为整型的链表:

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;//数据域struct SListNode* next;//指针域
}SLTNode;

 • 🌰3.链表的分类

链表可以分为单向链表、双向链表和循环链表。

🎉单向链表:每个节点只有一个指针指向下一个节点。

🎉双向链表:每个节点有两个指针,分别指向前一个节点和后一个节点。

 🎉循环链表:尾节点指向头结点。

链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2) 链表结构:

链表说明: 

🍋知识点二:单链表

 • 🌰1.顺序表的劣势

🎉中间/头部的插入删除,时间复杂度为O(N)

🎉增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗。

🎉增容一般是2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再进行插入了5个数据,后面就没有数据插入了,那么就浪费了95个数据空间。

总结:

中间/头部插入删除效率低下、增容降低运行效率、增容造成空间浪费。

然而我们接下来要学习的链表就完美地解决了顺序表的问题。

 • 🌰2.单链表动态申请节点

单链表申请节点的过程其实就是初始化的过程,也就是说单链表不需要单独初始化,需要用单链表的时候我们直接申请节点就可以了,当然,不论是头插、尾插还是指定位置插入都离不来申请节点的操作,所以我们将它写成一个函数:

SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}

申请一个结点newnode,首先if判空,不为空就将x存入数据域data,再让newnode的next指针置为NULL,因为没有下一个节点,所以必须置空,否则为野指针

 • 🌰3.链表元素的打印

注意:链表和顺序表是有所不同的,顺序表传过来的指针是肯定不会为空的,而链表传过来的指针是可能为空的比如说当链表中没有元素时,头指针所指向的就是NULL,如果在第一行写上断言就会有问题,所以不需要assert断言。

void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}

例如下列链表:

int main()
{//创建链表节点SLTNode* node1 = SLTBuyNode(1);SLTNode* node2 = SLTBuyNode(2);SLTNode* node3 = SLTBuyNode(3);SLTNode* node4 = SLTBuyNode(4);//让next指针指向下一个节点,最后一个为NULLnode1->next = node2;node2->next = node3;node3->next = node4;node4 = NULL;SLTPrint(node1);return 0;
}

 利用SLTPrint函数得到了结果如下:

1->2->3->4->NULL

 • 🌰4.单链表头部插入元素

向单链表的头部插入元素,我们只需要创建一个新节点,让这个新节点指向我们原来的第一个节点,再将第一个节点的指针*pphead指向newnode即可。

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}

注意,对于单链表来说,单链表的头部位置插入/删除、尾部位置插入/删除、指定位置插入/删除我们传参传的都是二级指针,因为我们都需要修改传过去的参数,也就是指向节点的指针。在之前的文章中,已经强调很多遍了,形参只是实参的一份临时拷贝,对形参的修改并不会影响实参。若传递结构体变量,是不会改变结构体变量本身的值的,如果我们需要在函数中改变一个变量的值,则需要传递这个值的地址,所以需要传址调用。

就比如头插SLTPushFront函数,我们需要将指向原先第一个节点的指针指向newnode,所以我们就要传第一个节点的地址的指针,也就是二级指针

其次,我们断言二级指针pphead不为空,目的是为了保证二级指针能够解引用得到一级指针*pphead,那有必要断言一级指针*pphead不为空吗?其实是没有必要的。如果*pphead为空,也就是没有任何节点的情况,其实就相当于申请新节点newnode,然后newnode->data为x,newnode->next为NULL。 

 • 🌰5.单链表尾部插入元素

向单链表的尾部插入元素,我们只需要创建一个新节点,让原先的单链表的最后一个节点的next指针指向这个新节点即可。

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);if (*pphead == NULL){newnode = *pphead;}else{SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}
}

单链表尾部插入元素,需要考虑*pphead为空的情况。头插不需要单独处理是因为及时*pphead为空,根据函数依然能创建一个节点,使得数据为x,next指针为NULL。而对于尾插而言,逻辑是让原来的最后一个节点的next指针指向newnode,而*pphead如果为NULL,也就是没有节点,那也就没有next指针,就没有办法处理这一情况,所以我们要单独处理。

如果*pphead为空,直接用SLTBuyNode申请一个新节点就可以了。对于*pphead不为空的情况,我们需要找到原单链表的最后一个节点。定义ptail指向第一个节点,利用while循环使得ptail指向原链表的最后一个节点,最后让ptail->next指针指向newnode即可。

 • 🌰6.指定位置之前插入数据

在pos节点的前面插入数据,需要先找到pos前一个的节点prev和创建的newnode节点,利用next指针将节点之间的关系设置好。

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && *pphead && pos);if (*pphead == pos){SLTPushFront(pphead, x);}else{SLTNode* newnode = SLTBuyNode(x);;SLTNode* prev = *pphead;while (prev->next = pos){prev = prev->next;}prev->next = newnode;newnode->next = pos;}
}

 在pos之前插入数据,需要得到三个节点:pos节点、pos前一个节点prev、newnode节点。由于需要得到pos前一个节点,所以当链表中只有一个节点的时候,我们没有办法找到,所以需要单独讨论,对于这种情况,其实就相当于头插SLTPushFront。

当链表中的节点大于1时,我们先创建newnode,然后利用while循环得到pos之前的prev节点,此时三个节点都有了,我们就可以进行操作了

 让prev的next指针指向newnode,再让newnode的next指针指向pos就可以了。

 • 🌰7.指定位置之后插入数据

在pos节点之后插入数据,我们需要得到pos节点、newnode节点和pos->next节点。

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);;newnode->next = pos->next;pos->next = newnode;
}

我们观察下图,发现在pos后插入数据其实并不需要第一个节点,所以我们的节点参数传的只有pos。 

分析上图,有一个地方需要大家注意,就是我们必须先让newnode->next指向pos->next节点,而不能先让pos->next=newnode,也就是代码的最后两行不可以颠倒!这时因为我们如果先修改了pos->next,实际上pos->next=newnode并不是让pos->next指向newnode,根据上图不难思考,这一步实际上直接转移了newnode,新创建的节点就没有用了。总之,这两行代码的顺序是不可以调换的以后我们再处理复杂的节点关系,可以先从newnode入手,因为newnode是新的节点,和原链表没有直接的关系。

 • 🌰8.单链表头部删除元素

单链表头部删除元素需要将第一个节点的next指针保存下来,这样才能保证free释放后的第一个节点next指针可以访问。

void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}

删除元素,一定要保证pphead和*pphead都不为NULL,其次,我们一定要在free之前就将(*pphead)->next指针保存下来,并且由于 * 的优先级低于 -> ,需要再*pphead上加上括号。

 注意,free只是放弃了对malloc开辟的空间的使用权限,*pphead还是可以继续使用的。

 • 🌰9.单链表尾部删除元素

尾删需要我们得到尾节点ptail和尾节点的前一个节点prev

void SLTPopBack(SLTNode** pphead)
{assert(pphead && *pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* ptail = *pphead;SLTNode* prev = *pphead;while (ptail->next){prev = ptail;ptail = ptail->next;}prev->next = NULL;free(ptail);ptail = NULL;}
}

一定要注意单链表中特殊情况需不需要分类讨论,标准就是如果特殊情况用普遍情况的代码也可以走得通就不需要单独讨论,反之则需要。

由于需要得到两个节点,当原链表中只有一个节点时,我们需要单独讨论。若只有一个节点, 我们直接释放掉它,再将它置为NULL就可以了。

 • 🌰10.删除指定位置的节点

删除指定的节点pos,需要得到pos节点、pos的前一个节点prev和pos->next节点。

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead && pos);if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}

 因为我们要得到prev节点,所以要对只有一个节点的情况分类讨论,这种情况直接用头删/尾删都是可以的。当节点数量大于1时,我们需要用while循环得到prev,再让prev->next指向pos->next就可以了。

此外,free释放后应该及时将pos置为NULL,这时一个好的习惯。 

 • 🌰11.删除指定位置之后的节点

删除pos的后一个节点,需要得到pos节点、pos->next节点和pos->next->next节点。

void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

由题意不难发现,这样的链表必须至少有两个节点,所以assert需要保证pos节点和pos->next节点都不为空。

所以,我们不需要讨论特殊情况。由于pos->next指针如果free释放,就不能再通过它来访问pos->next->next,所以我们令del=pos->next,先del->next赋给pos->next,然后我们free释放del,这样就可以了。

 • 🌰12.单链表的查找

查找单链表中数据为x的节点。

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead;while (pcur->next){if (pcur->data == x)return pcur;pcur = pcur->next;}return NULL;
}

我们用pcur遍历整个链表,整个过程需要保证pcur的next指针不为NULL,因为如果为NULL,它就已经是最后一个节点了。当我们查找完整个链表都没有存储数据为x的节点时,我们直接返回NULL就可以了。

 • 🌰13.单链表的销毁

当我们操作完单链表完成我们需要做的事情后,需要对链表进行销毁。

void SListDesTroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

用pcur遍历整个链表,先用next指针保存下一个节点,然后释放当前节点,重复这个过程,最后再将第一个接地那也置为NULL就可以了。由于需要保证pcur能够访问到pcur->next,所以assert需要断言pphead和*pphead。

🍋知识点三:单链表基本操作

 单链表的基本操作就是实现对顺序表元素的增、删、查、改,关于如何实现已经在前面都进行了讲解,也给出了代码,现在希望大家掌握之后通过下面给出的SList.h头文件,在SList.c文件中分别实现这些功能,并在test.c的main函数中测试:

#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//链表的打印
void SLTPrint(SLTNode* phead);//增加新节点
SLTNode* SLTBuyNode(SLTDataType x);//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);//尾删
void SLTPopBack(SLTNode** pphead);//头删
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);//销毁链表
void SListDesTroy(SLTNode** pphead);

• ✨SumUp结语

数据结构的学习一定要多画图,多理解,多思考,切忌直接抄写代码,就认为自己已经会了,一定到自己动手,才能明白自己哪个地方有问题。

 

如果大家觉得有帮助,麻烦大家点点赞,如果有错误的地方也欢迎大家指出~

 

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

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

相关文章

9.为什么有时候会“烫烫烫”——之函数栈桢

目录 1. 什么是函数栈帧 2. 理解函数栈帧能解决什么问题呢&#xff1f; 3. 函数栈帧的创建和销毁解析 3.1 什么是栈&#xff1f; 3.2 认识相关寄存器和汇编指令 3.3 解析函数栈帧的创建和销毁 小知识&#xff1a;烫烫烫~ Q&A 1. 什么是函数栈帧 我们在写C语言代码…

AI智能分析高精度烟火算法EasyCVR视频方案助力打造森林防火建设

一、背景 随着夏季的来临&#xff0c;高温、干燥的天气条件使得火灾隐患显著增加&#xff0c;特别是对于广袤的森林地区来说&#xff0c;一旦发生火灾&#xff0c;后果将不堪设想。在这样的背景下&#xff0c;视频汇聚系统EasyCVR视频融合云平台AI智能分析在森林防火中发挥着至…

广告归因数据回传:打造低成本、高ROI的oCPX模型

优化成本、提升转化&#xff0c;这一直是App效果广告买量投放的两大核心目标。广告归因中的数据回传正是因此而生&#xff0c;在投放oCPX类智能出价时&#xff0c;通过数据回传&#xff0c;数据算法不断对模型进行调优&#xff0c;广告投放机器人就会变得更加地聪明和智能&…

css 子元素 圆 均匀分布 展开动画

一般情况下使用scss就可以实现 import "math";#app {display: flex;align-items: center;justify-content: center;width: 200px;height: 200px;position: relative;border-radius: 50%;border: 1px solid #000;> span {position: absolute;display: flex;align-…

什么是HTTP/2?

HTTP/2&#xff08;原名HTTP 2.0&#xff09;即超文本传输协议第二版&#xff0c;使用于万维网。HTTP/2主要基于SPDY协议&#xff0c;通过对HTTP头字段进行数据压缩、对数据传输采用多路复用和增加服务端推送等举措&#xff0c;来减少网络延迟&#xff0c;提高客户端的页面加载…

Java数组(如果想知道Java中有关数组的知识点,那么只看这一篇就足够了!)

前言&#xff1a;数组对于每一门编程语言来说都是重要的数据结构之一&#xff0c;当然不同语言对数组的实现及处理也不尽相同,Java 语言中提供的数组是用来存储固定大小的同类型元素。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSD…

PixelYourSite PRO插件下载:提升网站性能与用户体验的终极解决方案

在数字时代&#xff0c;网站的性能和用户体验是企业成功的关键。PixelYourSite PRO插件是专为WordPress网站设计的一款高效工具&#xff0c;旨在通过先进的像素管理和优化技术&#xff0c;提升网站加载速度&#xff0c;增强用户互动&#xff0c;从而显著提高转化率。 一、为什…

智慧便民小程序源码系统 求职招聘+房产出租+相亲交友 带完整的安装代码包以及系统搭建教程

在数字化、智能化的今天&#xff0c;我们的生活节奏越来越快&#xff0c;对于各种服务的需求也越发多元化和个性化。为了满足广大市民对于便捷、高效、全面的服务需求&#xff0c;罗峰给大家分享一款智慧便民小程序源码系统&#xff0c;集求职招聘、房产出租、相亲交友三大功能…

深入理解指针(4)

目录 1. 字符指针变量2. 数组指针变量2.1 数组指针变量是什么&#xff1f;2.2 数组指针变量怎么初始化 3. ⼆维数组传参的本质4. 函数指针变量4.1 函数指针变量的创建4.2 函数指针变量的使⽤4.3 两段有趣的代码4.3.1 typedef 关键字 5. 函数指针数组6. 转移表 1. 字符指针变量 …

处理解决python

1、问题现象&#xff1a; ModuleNotFoundError: No module named pkg_resources 2、解决方案 安装pip install setuptools即可解决&#xff1a;

Windows环境下VSCode C无法跳转自动补全

前言&#xff1a; 本文记录了自己在配置 Windows环境下 VSCode C开发环境的遇到的问题和解决方法。 参考: vscode c语言没有代码提示_clangd提示不生效-CSDN博客 VSCODE无法跳转_vscode 不能跳转-CSDN博客 vscode c/c环境配置&#xff08;MinGW&#xff09;调用第三官方库…

华为OD机试 - 反射计数 - 矩阵(Java 2024 C卷 200分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

Vulstack红队评估(一)

文章目录 一、环境搭建1、网络拓扑2、web服务器(win7)配置3、域控&#xff08;winserver2008&#xff09;配置4、域内机器&#xff08;windows 2003&#xff09;配置5、调试网络是否通常 二、web渗透1、信息搜集2、端口扫描3、目录扫描4、弱口令5、phpmyadmin getshell日志gets…

OBS插件--声音波形显示

声音波形显示 波形显示是一个可以定制化的动态音频频谱图案&#xff0c;可以多音频进行可视化&#xff0c;对于音乐类主播必不可少&#xff0c;通过灵活的配置选项可以设计出非常个性化的频谱图形。 下面截图演示下操作步骤&#xff1a; 首先&#xff0c;打开 OBS直播助手 在…

探索生命奥秘的新征程:谷歌AlphaFold 3发布!

大家好&#xff01;今天和大家分享的是让人热血沸腾的消息——谷歌AlphaFold 3的发布&#xff01;这次的新版本可不仅仅是一次升级&#xff0c;而是一次生物科技的革命&#xff01; 回顾一下AlphaFold系列的历程&#xff0c;从AlphaFold 1的问世到AlphaFold 2的惊艳登场&#…

ChatGPT Web Midjourney一键集成最新版

准备工具 服务器一台 推荐使用浪浪云服务器 稳定 安全 有保障 chatgpt api 推荐好用白嫖的api 项目演示 项目部署 浏览器访问casaos 添加软件原添加 https://gitee.com/langlangy_1/CasaOS-AppStore-LangLangy/raw/master/chatmjd.zip 安装此软件 等待安装 安装后再桌面设置…

信息安全技术-分析题【太原理工大学】

没有历年题或明确说明大题会考什么&#xff0c;以下为个人猜测 简答题和选择判断占60&#xff0c;认真看题库和总结&#xff0c;能过d(^_^o) 好像说是加解密这类题会给公式让你直接套&#xff0c;但还是看一下基本原理&#xff0c;要不到时候蒙圈 1.加密算法步骤 图 1 是一个采…

Spring Boot | Spring Boot 整合 “异步任务“ 的实现

目录&#xff1a; 一、异步任务1.1 "无返回值" 异步任务调用 :① 创建项目② 编写 "异步调用方法" ( 使用 Async 注解 )③ "主程序启动类"中 开启基于 "注解" 的异步任务支持 ( 使用EnableAsync注解 )④ 编写 "控制层" 相关…

从0到1构建AI agent【零代码】

一、前言 想象一下&#xff0c;如果AI的想象力被彻底释放&#xff0c;那将是一场怎样的革命&#xff1f;“大语言模型不过是个贪吃蛇&#xff0c;而AI Agent却能创造出‘王者荣耀’。”这不仅是网上的一句戏言&#xff0c;它预示着一个不可逆转的趋势。比尔盖茨更是一语中的&am…

【全开源】Java共享台信息共享系统源码

特色功能 信息整合与共享&#xff1a;该平台提供一站式信息整合服务&#xff0c;将各种类型的信息资源进行汇聚&#xff0c;方便用户快速查找和获取所需资源。多种共享功能&#xff1a;支持信息共享、共享车位、共享会议室、共享电动车等多种共享功能&#xff0c;提高资源利用…