当前位置: 首页 > news >正文

[初阶数据结构】单链表

前言 

📚作者简介:爱编程的小马,正在学习C/C++,Linux及MySQL。

📚本文收录于初阶数据结构系列,本专栏主要是针对时间、空间复杂度,顺序表和链表、栈和队列、二叉树以及各类排序算法,持续更新!

📚相关专栏C++及Linux正在发展,敬请期待!

目录

前言 

1. 链表

1.1 链表的定义 

1.2 链表与顺序表相比的好处

1.3 链表的结构表示

1.3.1 链表的结构形式

1.3.2 链表的结构性质

1.4 单链表的实现

1.4.1 单链表的创建

 1.4.2 单链表的打印

1.4.3 单链表的动态内存申请

1.4.4 单链表的头插

1.4.5 单链表的尾插

1.4.6 单链表的头删

 1.4.7 单链表的尾删

1.4.8 单链表的查找

1.4.9 单链表的任意位置插入的前插

1.4.10 单链表任意位置的删除

2.单链表的完整代码

2.1 test.c测试函数代码

2.2 SList.h函数声明代码

2.3 SList.c函数实现代码

总结


1. 链表

1.1 链表的定义 

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

本文只介绍最简单的链表结构:单链表

1.2 链表与顺序表相比的好处

1、顺序表从中间插入/头部插入,时间复杂度是O(N),因为要一次往后挪动,但是单链表是O(1),大大节省了程序运行时间。

2、 顺序表每次需要增容,到后期增容很大的时候,需要拷贝数据、开辟新空间、释放旧空间。会有不小的损耗。链表直接开辟一个结构体大小的空间即可。

3、增容一般是两倍,但是我就想多插入几个仅此而已,会造成空间的大规模浪费。链表同样更加简单且占用空间小。

1.3 链表的结构表示

首先要给大家介绍一下,就是链表中的结点是一个结构体,结点中一个变量是存储数据的,另一个变量是存储结构体地址的,上一个结点存下一个结点的地址,从而链接起来。

1.3.1 链表的结构形式

1.3.2 链表的结构性质

1、从上图可以看出,链表在逻辑上是连续的,在物理地址上是不连续的

2、现实的结点是动态内存在堆区申请出来的

3、堆上申请的空间,是按照一定的规律来的,有些可能相同,有些可能不同。

1.4 单链表的实现

1.4.1 单链表的创建

上文我们提到了,结点是一个结构体,第一个结构体变量是存储数据的,第二个是存储下一个链表的地址的

typedef int SListDataType;typedef struct SListNode
{SListDataType data;struct SListNode* next;
}SLTNode;

 1.4.2 单链表的打印

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

为什么是这样子打印的?给大家说一下思想:

1、单链表定义了最后一个链表的地址为空指针,所以我们就定义了一个cur来遍历整个链表

2、每找到一个数据我们就打印,然后遍历链表指针cur就往后走一步, 怎么走?是不是next中存放了下一个结点的地址,那么把cur管理的结构体中next的地址赋值给cur是不是相当于向后走了一步。

1.4.3 单链表的动态内存申请

SLTNode* BuySLTNode(SListDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}

 比方说,我想申请一块动态内存空间,里面存储x的值,那么这个时候,就通过malloc在堆上申请一块空间,交给newnode管理,这时候把newnode中data的值赋值为x,newnode中next的值赋值为NULL后返回这块空间的地址。这是不是就很好的开辟了一个结点。如果开辟失败了就返回空指针。

1.4.4 单链表的头插

void SListPushFront(SLTNode** pphead, SListDataType x)
{SLTNode* newnode = BuySLTNode(x);assert(newnode);newnode->next = *pphead;*pphead = newnode;
}	

这里我们用newnode来管理开辟的新结点,如果开辟了失败了就不用往下了,开辟成功了往下走,我画个图来帮助大家理解上面代码的意思

1.4.5 单链表的尾插

我先给大家介绍一下,尾插有两种情况,第一种空链表,第二种,非空链表

先分析第一种情况,如果是空链表,是不是直接把newnode的地址给pphead是不是就可以了,

第二种情况,我给大家画个图一起来分析

void SListPushBack(SLTNode** pphead, SListDataType x)
{SLTNode* newnode = BuySLTNode(x);//空链表if (*pphead == NULL){*pphead = newnode;return;}//非空链表SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;
}

第二种情况,首先我们要尾插,是不是要先找尾部在哪里?尾部在哪里?是不是说tail->next是空指针,这个就是链表中最后一个元素了,找到了之后,就把tail->next存newnode的地址就可以。

1.4.6 单链表的头删

给大家说一下啊,如何删除单链表中的数?是不是只需要让上个结点存下下个结点的地址就好了。

那么头删就是让*pphead指向下下个结点,然后释放第一个结点就好了。 

那么,应该这么做,看代码

void SListPopFront(SLTNode** pphead)
{assert(*pphead);//链表只有一个值SLTNode* cur = *pphead;*pphead = cur->next;free(cur);cur = NULL;
}

画个图给大家理解一下:

 1.4.7 单链表的尾删

尾删就更简单了,还是第一步,找尾,第二部,释放空间即可。

void SListPopBack(SLTNode** pphead)
{assert(*pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* cur = *pphead;while (cur->next->next != NULL){cur = cur->next;}free(cur->next);cur->next = NULL;}
}

1.4.8 单链表的查找

在单链表中查找一个数,找到了就返回这个结点的地址,没找到就返回空指针。

SLTNode* SListFind(SLTNode* pphead, SListDataType x)
{assert(pphead);SLTNode* cur = pphead;while (cur){if (cur->data == x)return cur;elsecur = cur->next;}return NULL;
}

1.4.9 单链表的任意位置插入的前插

void SListInsertbefore(SLTNode** pphead, SLTNode* pos, SListDataType x)
{if (*pphead == NULL){SListPushFront(pphead, x);}//在pos前插入else{SLTNode* newnode = BuySLTNode(x);SLTNode* cur = *pphead;while (cur->next != pos){cur = cur->next;}cur->next = newnode;newnode->next = pos;}
}

首先啊,如果pphead没有值,那么就相当于头插,如果链表中有值,画个图给大家理解

1.4.10 单链表任意位置的删除

void SListErase(SLTNode** pphead, SLTNode* pos)
{assert(*pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* cur = *pphead;while (cur->next != pos){cur = cur->next;}cur->next = pos->next;free(pos);pos = NULL;}
}

2.单链表的完整代码

2.1 test.c测试函数代码

#include "SList.h"
SLTNode* Phead = NULL;
void Test1()
{//该函数测试头插和尾插SListPushFront(&Phead, 1);SListPushFront(&Phead, 2);SListPushFront(&Phead, 3);SListPushFront(&Phead, 4);SListPushFront(&Phead, 5);SListPushBack(&Phead, 2);SListPushBack(&Phead, 3);SListPushBack(&Phead, 4);PrintSList(Phead);
}void Test2()
{//该函数测试头删和尾删SListPushFront(&Phead, 1);SListPopBack(&Phead);PrintSList(Phead);
}
void Test3()
{//查找SListPushFront(&Phead, 1);SListPushFront(&Phead, 2);SListPushFront(&Phead, 3);SListPushFront(&Phead, 4);SLTNode* find = SListFind(Phead, 3);if (find)find->data = 30;PrintSList(Phead);
}
void Test4()
{//任意位置插入(前插)SListPushFront(&Phead, 1);SListPushFront(&Phead, 2);SListPushFront(&Phead, 3);SListPushFront(&Phead, 4);SLTNode* find1 = SListFind(Phead, 3);SLTNode* find2 = SListFind(Phead, 4);if (find1){SListInsertbefore(&Phead, find1, 40);SListEraseafter(find2);SListEraseafter(find1);}PrintSList(Phead);
}
int main()
{Test1();//Test2();//Test3();//Test4();return 0;
}

2.2 SList.h函数声明代码

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SListDataType;typedef struct SListNode
{SListDataType data;struct SListNode* next;
}SLTNode;void PrintSList(SLTNode* phead);
//头插
void SListPushFront(SLTNode** pphead, SListDataType x);
//尾插
void SListPushBack(SLTNode** pphead, SListDataType x);
//头删
void SListPopFront(SLTNode** pphead);
//尾删
void SListPopBack(SLTNode** pphead);
//查找
SLTNode* SListFind(SLTNode* pphead, SListDataType x);
//在pos之后插入x
void SListInsertbefore(SLTNode** pphead,SLTNode* pos, SListDataType x);
//在pos之后插入x
void SListInsertafter(SLTNode* pos, SListDataType x);
//删除pos位置上的值
void SListErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的位置
void SListEraseafter(SLTNode* pos);

2.3 SList.c函数实现代码

#include "SList.h"
void PrintSList(SLTNode* phead)
{SLTNode* cur = phead;while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL");
}SLTNode* BuySLTNode(SListDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}
void SListPushFront(SLTNode** pphead, SListDataType x)
{SLTNode* newnode = BuySLTNode(x);assert(newnode);newnode->next = *pphead;*pphead = newnode;
}	void SListPushBack(SLTNode** pphead, SListDataType x)
{SLTNode* newnode = BuySLTNode(x);//空链表if (*pphead == NULL){*pphead = newnode;return;}//非空链表SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;
}void SListPopFront(SLTNode** pphead)
{assert(*pphead);//链表只有一个值SLTNode* cur = *pphead;*pphead = cur->next;free(cur);cur = NULL;
}void SListPopBack(SLTNode** pphead)
{assert(*pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* cur = *pphead;while (cur->next->next != NULL){cur = cur->next;}free(cur->next);cur->next = NULL;}
}SLTNode* SListFind(SLTNode* pphead, SListDataType x)
{assert(pphead);SLTNode* cur = pphead;while (cur){if (cur->data == x)return cur;elsecur = cur->next;}return NULL;
}
void SListInsertbefore(SLTNode** pphead, SLTNode* pos, SListDataType x)
{if (*pphead == NULL){SListPushFront(pphead, x);}//在pos前插入else{SLTNode* newnode = BuySLTNode(x);SLTNode* cur = *pphead;while (cur->next != pos){cur = cur->next;}cur->next = newnode;newnode->next = pos;}
}void SListInsertafter(SLTNode* pos, SListDataType x)
{assert(pos);//只有一个SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}void SListErase(SLTNode** pphead, SLTNode* pos)
{assert(*pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* cur = *pphead;while (cur->next != pos){cur = cur->next;}cur->next = pos->next;free(pos);pos = NULL;}
}void SListEraseafter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* cur = pos->next;pos->next = cur->next;free(cur);cur = NULL;
}

总结

1、单链表其实不难,大家一定要搞清楚指针和结构体

2、一定要动手实践一下

3、其实数据结构就是围绕着数据的增删查改显示这几个点,所以一定要搞清楚每一个代码实现的逻辑。

如果这份博客对大家有帮助,希望各位给小马一个大大的点赞鼓励一下,如果喜欢,请收藏一下,谢谢大家!!!
制作不易,如果大家有什么疑问或给小马的意见,欢迎评论区留言。

相关文章:

[初阶数据结构】单链表

前言 &#x1f4da;作者简介&#xff1a;爱编程的小马&#xff0c;正在学习C/C&#xff0c;Linux及MySQL。 &#x1f4da;本文收录于初阶数据结构系列&#xff0c;本专栏主要是针对时间、空间复杂度&#xff0c;顺序表和链表、栈和队列、二叉树以及各类排序算法&#xff0c;持…...

项目使用git开发流程

第一步 项目初期&#xff1a;领导负责的工作 01 创建仓库&#xff1a;在码云上面创建仓库地址&#xff0c;创建完成后点击初始化README&#xff1a;郝陶涛/vue-tea 02 领导在桌面上将代码克隆下来&#xff1a;将代码克隆下来之后&#xff0c;切换到代码内部&#xff0c;使用g…...

Day 28 MySQL的数据备份与恢复

数据备份及恢复 1.概述 ​ 所有备份数据都应放在非数据库本地&#xff0c;而且建议有多份副本 备份&#xff1a; 能够防止由于机械故障以及人为误操作带来的数据丢失&#xff0c;例如将数据库文件保存在了其它地方 冗余&#xff1a; 数据有多份冗余&#xff0c;但不等备份&…...

PackageKit的使用(三)疑问篇

本篇主要是一些疑问归纳&#xff0c;不做具体的函数分析&#xff0c;但是会给出关键点&#xff0c;查看源码就会很清楚了 apt source PackageKit 1. org.freedesktop.PackageKit D-Bus 接口介绍 D-Bus API Reference: PackageKit Reference Manual c库的接口可以看源码。 2.…...

【Linux】17. 进程间通信 --- 管道

1. 什么是进程间通信(进程间通信的目的) 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;多个进程之间共享同样的资源。 通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#xff0c;通知它&#xff08;它们&#xff09;发生了…...

有哪些有效的复习方法可以帮助备考软考?

软考目前仍然是一个以记忆为主、理解为辅的考试。学过软考的朋友可能会感到困惑&#xff0c;因为软考的知识在日常工作中有许多应用场景&#xff0c;需要理解的地方也很多。但为什么我说它是理解为辅呢&#xff1f;因为这些知识点只要记住了&#xff0c;都不难理解&#xff0c;…...

【MySQL | 第九篇】重新认识MySQL锁

文章目录 9.重新认识MySQL锁9.1MySQL锁概述9.2锁分类9.2.1锁的粒度9.2.2锁的区间9.2.3锁的性能9.2.4锁的级别 9.3拓展&#xff1a;意向锁9.3.1意向锁概述9.3.2意向锁分类9.3.3意向锁作用&#xff08;1&#xff09;意向锁的兼容互斥性&#xff08;2&#xff09;例子1&#xff08…...

含义:理财风险等级R1、R2、R3、R4、R5

理财风险等级R1、R2、R3代表什么&#xff0c;为什么R1不保本&#xff0c;R2可能亏损 不尔聊投资https://author.baidu.com/home?frombjh_article&app_id1704141696580953 我们购买理财产品的时候&#xff0c;首先都会看到相关产品的风险等级。风险等级约定俗成有5级&…...

ICode国际青少年编程竞赛- Python-2级训练场-列表入门

ICode国际青少年编程竞赛- Python-2级训练场-列表入门 1、 Dev.step(3)2、 Flyer.step(1) Dev.step(-2)3、 Flyer.step(1) Spaceship.step(7)4、 Flyer.step(5) Dev.turnRight() Dev.step(5) Dev.turnLeft() Dev.step(3) Dev.turnLeft() Dev.step(7) Dev.turnLeft() Dev.…...

【设计模式】14、strategy 策略模式

文章目录 十四、strategy 策略模式14.1 map_app14.1.1 map_app_test.go14.1.2 map_app.go14.1.3 navigate_strategy.go 十四、strategy 策略模式 https://refactoringguru.cn/design-patterns/strategy 需求: client 知道很多不同的策略, 希望在运行时切换. 场景示例: 就像高…...

C++类和对象(基础篇)

前言&#xff1a; 其实任何东西&#xff0c;只要你想学&#xff0c;没人能挡得住你&#xff0c;而且其实学的也很快。那么本篇开始学习类和对象&#xff08;C的&#xff0c;由于作者有Java基础&#xff0c;可能有些东西过得很快&#xff09;。 struct在C中的含义&#xff1a; …...

Oracle导入数据中文乱码问题处理,修改客户端字符编码跟数据库的一致

前提&#xff1a;SQL文件打开其中中文字符是正常显示&#xff0c;保证导出文件中文字符正常。通过sqlplus命令导入SQL文件出现乱码&#xff0c;这是因为客户端跟数据库的字符集不一致导致出现乱码问题。 要SQL导入的中文正常&#xff0c;要确保执行导入命令的客户端字符编码跟…...

【与 Apollo 共创生态:展望自动驾驶全新未来】

1、引言 历经七年的不懈追求与创新&#xff0c;Apollo开放平台已陆续推出了13个版本&#xff0c;汇聚了来自全球170多个国家与地区的16万名开发者及220多家合作伙伴。随着Apollo开放平台的不断创新与发展&#xff0c;Apollo在2024年4月19日迎来了Apollo开放平台的七周年大会&a…...

【webrtc】MessageHandler 5: 基于线程的消息处理:以PeerConnection信令线程为例

peerconn的信令是通过post 消息到自己的信令线程消息来处理的PeerConnectionMessageHandler 是具体的处理器G:\CDN\rtcCli\m98\src\pc\peer_connection_message_handler.hMachinery for handling messages posted to oneself PeerConnectionMessageHandler 明确服务于 signalin…...

计算机网络 3.2网络体系结构

第二节 网络体系结构 一、网络协议 1.定义&#xff1a; ①通信双方共同遵守的规则。 ②为网络数据交换制定的规则、约定与标准。 ③网络实体之间通信时有关信息传输顺序、信息格式、信息内容的约定或规则。 2.协议三要素&#xff1a; 语法&#xff1a;确定协议元素的格式…...

连接HiveMQ代理器实现MQTT协议传输

先下载MQTTX: MQTTX: Your All-in-one MQTT Client Toolbox 使用线上免费的MQTTX BROKER:The Free Global Public MQTT Broker | Try Now | EMQ 打开MQTTX&#xff0c;创建连接&#xff0c;点击NEW SUBSCRIPTION,创建一个主题&#xff0c;这里使用test/topic,在下面Json中填写…...

springcloud报错:Failed to start bean‘webServerStartStop‘

如果你正在使用nacos进行服务注册&#xff0c;然后报一下错误&#xff1a; 那就说明的nacos没有打开&#xff0c;所以找到你的下载nacos的文件夹 好了&#xff0c;错误完美解决~...

el-checkbox 无法动态设置勾选状态

问题 cheked 值动态变化&#xff0c;但是勾选状态无法动态改变 解决 v-model 与:checked 同时使用 <el-checkbox class"add-shop-check" v-model"renderData[0].isCheck" :checked"renderData[0].isCheck" change"checked > selec…...

车规级低功耗汽车用晶振SG-9101CGA

车规级晶振SG-9101CGA属于爱普生9101系列&#xff0c;是一款可编程晶振。SG-9101CGA车规级晶振采用2.5x2.0mm封装&#xff0c;利用PLL技术生产&#xff0c;此款振荡器的频率范围从0.67M~170MHZ任一频点可选&#xff0c;步进1ppm&#xff0c;采用标准CMOS输出&#xff0c;最大输…...

企业是保留传统的MES还是换新的MES?

在选择上MES系统的时候&#xff0c;企业可以根据自身所处行业不同、当前阶段不同&#xff0c;以及业务需求的差异&#xff0c;对症下药&#xff0c;选择适合自己的解决方案。对于有些企业本来就有MES系统&#xff0c;但是已经过时过旧&#xff0c;就要考虑换新的MES系统了. 保留…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...