[初阶数据结构】单链表

前言 

📚作者简介:爱编程的小马,正在学习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、其实数据结构就是围绕着数据的增删查改显示这几个点,所以一定要搞清楚每一个代码实现的逻辑。

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

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

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

相关文章

如何使用client-go构建pod web shell

代码示例及原理 原理是利用websocket协议实现对pod的exec登录&#xff0c;利用client-go构造与远程apiserver的长连接&#xff0c;将对pod容器的输入和pod容器的输出重定向到我们的io方法中&#xff0c;从而实现浏览器端的虚拟终端的效果消息体结构如下 type Connection stru…

Meta更低的训练成本取得更好的性能: 多token预测(Multi-Token Prediction)

Meta提出了一种透过多token预测(Multi-token Prediction)来训练更好、更快的大型语言模型的方法。这篇论文的重点如下: 训练语言模型同时预测多个未来的token,可以提高样本效率(sample efficiency)。 在推论阶段,使用多token预测可以达到最高3倍的加速。 论文的主要贡献包括: …

ES集群数据备份与迁移

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、文章涉及概念讲解二、操作步骤1.创建 snapshot repository操作主机hadoop1分别操作从机hadoop2和hadoop3 2. 查看仓库信息3. 备份索引&#xff0c;生成快照…

【S32K UDS BootLoader】-1.1-Unified bootloader Demo和ECUBus工具的使用

<--返回「Autosar_MCAL高阶配置」专栏主页--> 目录 1 下载S32K1/S32K3/S12Z Unified bootloader Demo 1.1 在S32DS中编译S32K312_CAN_bootloader_RTD2d0工程并烧录 2 ECUBus工具使用 2.1 PCAN环境搭建 1.1.1 安装PCAN驱动 1.1.2 安装PCAN-View 2.2 下载并安装ECU…

C语言 | Leetcode C语言题解之第77题组合

题目&#xff1a; 题解&#xff1a; int** combine(int n, int k, int* returnSize, int** returnColumnSizes) {int* temp malloc(sizeof(int) * (k 1));int tempSize 0;int** ans malloc(sizeof(int*) * 200001);int ansSize 0;// 初始化// 将 temp 中 [0, k - 1] 每个…

回答篇:测试开发高频面试题目

引用之前文章&#xff1a;《测试开发高频面试题目》 https://blog.csdn.net/qq_41214208/article/details/138193469?spm1001.2014.3001.5502 本篇文章是回答篇&#xff08;持续更新中&#xff09; 1. 什么是测试开发以及其在软件开发流程中的作用。 a. 测试开发是指测试人员或…

关于Anaconda常用的命令

常用命令 查看当前环境下的环境&#xff1a;conda env list查看当前conda的版本&#xff1b;conda --version conda create -n your_env_name pythonX.X&#xff08;2.7、3.6等)命令创建python版本为X.X。名字为your_env_name的虚拟环境。your_env_name文件可以在Anaconda安装…

收银系统源码--什么是千呼智慧新零售系统?

千呼智慧新零售系统是一套针对零售行业线上线下一体化收银系统。给门店提供线下称重收银、o2o线上商城、erp进销存、精细化会员管理、丰富营销插件等一体化解决方案。多端数据打通&#xff0c;实现线上线下一体化&#xff0c;提升门店工作效率&#xff0c;实现数字化升级&#…

前端项目加载离线的百度地图,利用工具进行切指定区域的地图影像,自定义图层getTilesUrl

百度地图在开发中我们经常使用&#xff0c;但是有些项目是需要在内网进行&#xff0c;这时候我们不得不考虑项目中一些功能需要请求外网静态资源&#xff0c;比如百度地图。只有把包下载到本地&#xff0c;才能让静态资源文件的正常的访问。 目录 获取百度地图开发秘钥 引入在…

Java | Leetcode Java题解之第78题子集

题目&#xff1a; 题解&#xff1a; class Solution {List<Integer> t new ArrayList<Integer>();List<List<Integer>> ans new ArrayList<List<Integer>>();public List<List<Integer>> subsets(int[] nums) {dfs(0, nums…

牛客网刷题 | BC81 KiKi求质数个数

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 描述 KiKi知道了什么是质…

【离散数学】集合上二元关系性质判定的实现(c语言实现)

实验要求 关系矩阵的初始化和打印 我们将关系矩阵存入一个二维数组中&#xff0c;因为集合元素个数不会超过5个所以就用一个5行5列二维数组来表示。 在我们得到了集合元素个数之后我们就可以对数组进行0,1随机赋值 //初始关系矩阵 void init_matrix(int array[][5], int n) {…

多核DSP并行计算跨平台通信解决方案

并行计算的核心是计算节点以及节点间的通信与协调机制。OpenMP虽然给开发者提供了极易上手的增量式开发方式&#xff0c;但是OpenMP在与复杂架构的MCSDK结合后&#xff0c;工具与代码产生了大量不可调试的黑盒子&#xff0c;更是决定了它不能用于关键任务领域&#xff0c;如军工…

算法学习Day2——单调栈习题

第一题&#xff0c;合并球 题解&#xff1a;一开始写了一次暴力双循环&#xff0c;直接O(n^2)严重超时&#xff0c;后面于是又想到了O(n)时间复杂度的链表&#xff0c;但是还是卡在 最后一个数据会TLE&#xff0c;我也是高兴的拍起来安塞腰鼓和华氏护肤水&#xff0c;后面学长给…

基于模糊控制的AMT自动变速汽车换档智能控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于模糊控制的AMT自动变速汽车换档智能控制系统simulink建模与仿真。 2.系统仿真结果 输入的V&#xff0c;Ac&#xff0c;a 输出的档位&#xff1a; 3.核心程序与模型 版…

【C语言】static关键字用法

目录 一、static修饰局部变量 二、static修饰全局变量 三、static修饰函数 一、static修饰局部变量 首先我们来看两段代码: 代码1&#xff08;不加static&#xff09; #include <stdio.h> void test() {int i 0;i;printf("%d ", i); } int main() {int i…

VMvare如何更改虚拟机内共享文件夹的挂载点

更改虚拟机内共享文件夹的路径 进入目录 /etc/init.d ,并找到vmware-tools文件 里面有配置项 vmhgfs_mnt"/mnt/hgfs" 将引号内的内容更改为你需要挂载的路径,重启即可 注意挂载的路径不能是 “/”&#xff0c;必须根目录下的某个文件夹&#xff0c;或者其子文件夹 …

使用Docker安装Yapi接口管理工具

简介&#xff1a; YAPI 是由去哪儿网移动架构组开发的一款可视化接口管理工具。它具有可视化管理、高效易用、功能强大等特点。它提供了便捷的接口创建、发布和维护方式&#xff0c;开发人员可以通过简单的操作实现接口管理。 YAPI 还支持类似 postman 的接口调试&#xff0c;对…

GPU通用计算介绍

谈到 GPU &#xff08;Graphics Processing Unit&#xff0c;图形显示卡&#xff09;大多数人想到的是游戏、图形渲染等这些词汇&#xff0c;图形处理确实是 GPU 的一大应用场景。然而人们也早已关注到它在通用计算上的巨大潜力&#xff0c;并提出了 GPGPU (General-purpose co…

Android进阶之路 - 静态会员进度条

年后这个新版本加入了VIP模块&#xff0c;有幸正好由我来负责&#xff0c;可以再积累一下这方面的知识。 那段时间看了一本书&#xff0c;书中说到初级码农的特性之一就是完全集中于某些功能&#xff0c;忽略了了很多成长机会&#xff0c;所以重复性劳作带来的成长值有限&#…