单链表(C语言详细版)

1. 链表的概念及结构

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

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

车厢是独立存在,且每节车厢都有车门。想象一下这样的场景,假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下如何从车头走到车尾?

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

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

与顺序表不同的是,链表里的每节“车厢”都是独立申请下来的空间,我们称之为“结点/节点”。节点的组成主要有两个部分:当前节点要保存的数据和保存下一个节点的地址(指针变量)。图中指针变量 plist 保存的是第一个节点的地址,我们称 plist 此时“指向”第一个节点,如果我们希望 plist “指向”第二个节点时,只需要修改 plist 保存的内容为 0x0012FFA0。

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

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

假设当前保存的节点为整型:

// 定义节点的结构
// 数据 + 指向下一个节点的指针
typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;		// 存储的节点数据struct SListNode* next; // 指针变量用来保存下一个节点的地址
}SLTNode;

当我们想要保存一个整型数据时,实际是向操作系统申请了一块内存,这个内存不仅要保存整型数据,也需要保存下一个节点的地址(当下一个节点为空时保存的地址为空)。

当我们想要从第一个节点走到最后一个节点时,只需要在前一个节点拿上下一个节点的地址(下一个节点的钥匙)就可以了。

给定的链表结构中,如何实现节点从头到尾的打印?

补充:

1. 链式结构在逻辑上是连续的,在物理结构上不一定连续;

2. 节点一般是从堆上申请的;

3. 从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能是连续,可能不连续。

2. 单链表的实现

2.1 链表的打印

// 链表的打印
void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;while (pcur)	// pcur != NULL{printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}

测试程序:首先我们得先开辟新的空间,然后创建几个新的节点,再把所创建的新节点关联起来,最后调用打印函数接口,打印数据(注意:最后一个节点存储的下一个节点的地址要为NULL

void SListTest01()
{// 链表是由一个一个的节点组成// 创建几个节点SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));node1->data = 1;SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));node2->data = 2;SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));node3->data = 3;SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));node4->data = 4;// 将四个节点连接起来node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;// 调用链表的打印SLTNode* plist = node1;SLTPrint(plist);
}int main()
{SListTest01();return 0;
}

运行结果:

2.2 链表的尾插

// 链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);

如果我们要在链表的最后一个节点尾插一个新的节点,首先要先找到尾节点,再将尾节点和新节点连接起来。不过,在插入之前我们得先申请一个新的节点空间。这里需要注意的一个点:我们形参要改变实参必须要传地址(即要用指针来接收),那为什么形参不用一级指针而用二级指针呢?因为,我们创建的节点是一个结构体指针 SLTNode* ,所以我们得用二级指针来接收。

知道了原理,我们接下来就可以编写程序,首先得判断头节点的地址 &plist (pphead)不能为空,然后申请一个新的节点空间。好,接下来我们要分两种情况:1. 链表是空链表;2. 链表是非空链表。空链表:我们直接把新节点作为头节点;非空链表:我们就正常的尾插。

尾插接口函数:

// 申请节点函数
SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));if (newNode == NULL){perror("malloc fail!");exit(1);	// 正常退出是0,非正常退出是非0}newNode->data = x;newNode->next = NULL;return newNode;
}// 链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);	// pphead 不能为NULL// *pphead 就是指向第一个节点的指针// 空链表和非空链表// 申请新的节点SLTNode* newNode = SLTBuyNode(x);if (*pphead == NULL){*pphead = newNode;}else{// 找尾SLTNode* ptail = *pphead;while (ptail->next)	// ptail->next != NULL{ptail = ptail->next;}// ptail指向的就是尾节点ptail->next = newNode;}
}

测试程序:尾插 1 2 3 4

void SListTest02()
{SLTNode* plist = NULL;// 测试尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);
}int main()
{SListTest02();return 0;
}

运行结果:

2.3 链表的头插

// 链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);

头插我们也得分两种情况:1. 链表是空链表;2. 链表是非空链表。

首先得判断头节点的地址 &plist (pphead)不能为空,然后申请一个新的节点空间。让新节点 newNode 的下一个节点的地址 newNode->next 指向头节点 *pphead ,再新节点 newNode 作为新的头节点 *pphead。

头插接口函数:

// 链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{// 空链表和非空链表都能使用此方法assert(pphead);	// pphead 不能为NULL// 申请新的节点SLTNode* newNode = SLTBuyNode(x);newNode->next = *pphead;*pphead = newNode;
}

测试程序:尾插 1 2 3 4,再头插 6 7 8

void SListTest02()
{SLTNode* plist = NULL;// 测试尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);// 测试头插SLTPushFront(&plist, 6);SLTPrint(plist);SLTPushFront(&plist, 7);SLTPrint(plist);SLTPushFront(&plist, 8);SLTPrint(plist);
}int main()
{SListTest02();return 0;
}

运行结果:

我们测试一下空链表是不是也可行:

void SListTest02()
{SLTNode* plist = NULL;// 测试头插SLTPushFront(&plist, 6);SLTPrint(plist);SLTPushFront(&plist, 7);SLTPrint(plist);SLTPushFront(&plist, 8);SLTPrint(plist);
}int main()
{SListTest02();return 0;
}

运行结果:可行!

2.3 链表的尾删

// 链表的尾删
void SLTPopBack(SLTNode** pphead);

链表节点的删除不是单纯把要删除的节点给free掉,而是要找最后一个节点的前一个节点,把这个节点的下一个节点的地址置为NULL。

尾删我们也要分两种情况:1. 只有单节点;2. 多节点。单节点:我们直接把头节点给释放掉,然后置为空。多节点:我们先找到最后一个节点,怎么找呢?定义一个 ptail 结构体指针,遍历链表,当 patil->next 为 NULL ,说明此时的 ptail 就是尾节点。我们还要找尾节点的前一个节点,这时我们就要定义一个 prev 结构体指针,把找尾节点的 prev = ptail 保存一下,最后 patil = NULL,说明前一次找的就是尾节点的前一个节点。

// 链表的尾删
void SLTPopBack(SLTNode** pphead)
{// 链表不能为空assert(pphead && *pphead);// 链表只有一个节点if ((*pphead)->next == NULL)	// -> 优先级高于 *{free(*pphead);*pphead = NULL;}else{// 链表有多个节点SLTNode* prev = *pphead;SLTNode* ptail = *pphead;while (ptail->next){prev = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;prev->next = NULL;}
}

测试程序:尾插 1 2 3 4,然后尾删,一直删到只剩一个节点,多节点和单节点一起判断

void SListTest02()
{SLTNode* plist = NULL;// 测试尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);// 测试尾删SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);
}int main()
{SListTest02();return 0;
}

运行结果:

2.4 链表的头删

// 链表的头删
void SLTPopFront(SLTNode** pphead);

在释放头节点之前,我们要先用一个指针 next 保存头节点 *pphead 下一个节点的地址,然后释放头节点,再把 next 指针作为新的头节点。

// 链表的头删
void SLTPopFront(SLTNode** pphead)
{// 链表不能为空assert(pphead && *pphead);// 多个节点和单个节点都能使用SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}

测试程序:

void SListTest02()
{SLTNode* plist = NULL;// 测试尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);// 测试头删SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);
}int main()
{SListTest02();return 0;
}

运行结果:尾插 1 2 3 4,然后头删,一直删到只剩一个节点,多节点和单节点一起判断

2.5 链表的查找

// 链表的查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

遍历链表数据,找到了,就返回对应的结构体指针,没有找到,就返回NULL。

// 链表的查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}

测试程序:

void SListTest02()
{SLTNode* plist = NULL;// 测试尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);// 测试查找SLTNode* find = SLTFind(plist, 3);if (find == NULL){printf("没有找到!\n");}else{printf("找到了!\n");}
}int main()
{SListTest02();return 0;
}

运行结果:

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

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

相关文章

资料分析笔记整理

提升技巧多做题、少动笔、多分析 资料分析认识 国考一般20题(24~28分钟) 统计材料的类型包括单纯的文字、表格、图形以及由这些元素组成的复合类型材料 文字性材料:(30~60秒) 多段落型文字材料(时间、关键词、结构) 孤立段落文字材料(时间、关键词、标点[。;]) 表…

VMware vSAN替换存储解决方案如何选择?

What is vSAN ? 是一款软件定义的企业存储解决方案,支持超融合基础架构系统。vSAN与VMware vSphere 完全集成在一起,作为ESXi Hypervisor内的分布式软件层,通过整合、池化ESXi各个主机上的存储资源,为vSphere虚拟化平…

施罗德数列SQL实现

在组合数学中,施罗德数用来描述从(0,0)到(n,n)的格路中,只能使用(1,0)、(0,1)、(1,1)三种移动方式,始终位于对角线下方且不越过对角线的路径数 DECLARE n INT 10 DECLARE i INT DECLARE rst INT DECLARE old INT1CREATE TABLE #rst (i INT ,rst int )INSERT INTO #rst values(…

数据结构双向循环链表

主程序 #include "fun.h" int main(int argc, const char *argv[]) { double_p Hcreate_head(); insert_head(H,10); insert_head(H,20); insert_head(H,30); insert_head(H,40); insert_tail(H,50); show_link(H); del_tail(H); …

UE5.3-基础蓝图类整理一

常用蓝图类整理: 1、获取当前关卡名:Get Current LevelName 2、通过关卡名打开关卡:Open Level(by name) 3、碰撞检测事件:Event ActorBeginOverlap 4、获取当前player:Get Player Pawn 5、判断是否相等&#xff1…

回溯算法-以学生就业管理系统为例

1.回溯算法介绍 1.来源 回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。 用回溯算法解决问题的一般步骤: 1、 针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。 2 、确定易于搜…

可视化作品集(09):可视化运维大屏不可或缺。

可视化大屏在可视化运维上有很多价值,而且应用十分普遍,本文给老铁们分享一下。 1. 实时监控:可视化大屏可以实时展示系统运行状态、设备状态、生产数据等信息,使运维人员能够及时发现问题并做出相应的处理。 2. 数据分析&#x…

文件上传漏洞:upload-labs靶场安装和实践

一、upload-labs靶场安装 安装:Windows下的Upload-labs环境搭建(Upload文件夹不存在报错)_upload-labs文件夹不存在-CSDN博客 当安装好phpstudy之后,在网址栏输入:localhost或127.0.0.1,如果没问题,就将下…

鸿蒙系统创建签名文件及使用创建签名文件打包并安装

* 第一步 第二步:创建.p12文件,点击New如果有的话就Choose Existing 填好下面信息 点击Next进入到下面界面 开始生成csr文件如下图 点击OK–>Finish 文件保存在了下面目录 第三步 1.访问华为开发者平台,登录开发者账号,进…

影视行业的人工智能与-【机器学习】:案例分析

欢迎关注小知:知孤云出岫 目录 引言AI和ML在影视行业的当前应用AI和ML对影视行业的未来影响案例研究:AI生成动画视频目标工具和库数据收集模型训练视频生成 结论参考文献 引言 人工智能(AI)和机器学习(ML&#xff09…

挂耳式耳机哪款比较好、挂耳式耳机推荐高性价比

近年来,开放式耳机行业蓬勃发展,受到了越来越多消费者的喜爱,然而,这里边也夹着不专业的产品,低质量的生产不仅不能带来舒适的体验,甚至可能对耳朵造成潜在的伤害。挂耳式耳机哪款比较好?为了帮…

JavaWeb__正则表达式

目录 1. 正则表达式简介2. 正则表达式体验2.1 验证2.2 匹配2.3 替换2.4 全文查找2.5 忽略大小写2.6 元字符使用2.7 字符集合的使用2.8 常用正则表达式 1. 正则表达式简介 正则表达式是描述字符模式的对象。正则表达式用于对字符串模式匹配及检索替换,是对字符串执行…

Obsidian 文档编辑器

Obsidian是一款功能强大的笔记软件 Download - Obsidian

Kubernetes 为pod指定DNS

在k8s里面,默认创建pod会给pod默认分配一个默认的dns,这个dns是哪来的呢?可不可以改成其他的dns呢? 先进入到pod里面来,可以看到这里面默认设置的DNS服务器,这个服务器地址为10.96.0.10。这个地址是k8s自动…

企业级网关设计

tips:本文完全来源于卢泽龙!!! 一、Gateway概述 1.1设计目标 1.2gateway基本功能 中文文档参考:https://cloud.tencent.com/developer/article/1403887?from15425 三大核心: 二、引入依赖和yaml配置…

大话光学原理:2.最短时间原理、“魔法石”与彩虹

一、最短时间原理 1662年左右,费马在一张信纸的边角,用他那著名的潦草笔迹,随意地写下了一行字:“光在两点间选择的路,总是耗时最少的。”这句话,简单而深邃,像是一颗悄然种下的种子&#xff0c…

【C语言】volatile 关键字详解

在C语言中,volatile关键字用于声明一个变量,告知编译器该变量的值可能会被程序之外的某些因素(如硬件或其他并发线程)改变。因此,编译器在优化代码时不能对这个变量做假设,也不能优化掉对它的读取或写入操作…

【分布式系统】ceph部署(命令+截图巨详细版)

目录 一.存储概述 1.单机存储设备 2.单机存储的问题 3.商业存储 4.分布式存储​编辑 4.1.什么是分布式存储 4.2.分布式存储的类型 二.ceph概述 1.ceph优点 2.ceph架构 3.ceph核心组件 4.OSD存储后端 5.ceph数据存储过程 6.ceph版本发行生命周期 7.ceph集群部署 …

使用pyqt界面化部署

使用pyqt界面化部署 文章目录 前言一、软件介绍总结 前言 pyqtopencv开发的图像识别qt界面 目前共有五个主要界面:软件介绍界面、省份识别、浙产识别、产地识别界面、以及自定义识别页面。 三叶青图像识别研究简概 一、软件介绍 总结 开发这个图像识别的qt界面&a…

插入排序算法(C语言版)

直接插入排序 插入排序(insert sort)是一种简单的排序算法,它的工作原理与手动整理一副牌的过程非常相似。 具体来说,我们在未排序区间选择一个基准元素,将该元素与其左侧已排序区间的元素逐一比较大小,并…