C语言----单链表的实现

前面向大家介绍了顺序表以及它的实现,今天我们再来向大家介绍链表中的单链表。

1.链表的概念和结构

1.1 链表的概念

链表是一种在物理结构上非连续,非顺序的一种存储结构。链表中的数据的逻辑结构是由链表中的指针链接起来的。

1.2 链表的结构

链表的结构与火车相似。

3e2efeadaa264c3b85afa3c54c291c68.jpeg

fec2696487ea4e3dba19cddc85e1a605.jpeg

火车是由一节一节的车厢构成的,并且各个车厢之间是相互独立的,且每个车厢都有属于自己的锁。假设火车上车厢的门都是锁上的状态,每个门都要对应的锁来开门,那我们如何快速的从第一个车厢走到最后一个车箱呢?

答案很简单,我们只要把下一节的车厢的钥匙放在上一节车厢就行了。

链表也是如此,车厢对应到链表中就是节点

所以链表是由一个个节点组成的,每个节点由存储的数据和指向下一个节点的指针组成的。

为什么需要指针呢?

因为链表在物理结构上是不连续的,由于连表中的节点的地址是由计算机随机分配的,我们并不能清楚的知道各个节点的具体位置,这时候就需要指针了。通过指针我们就能知道每个节点的位置。

53a3ff77d2f04c1f8606ab16adb7d5fc.jpeg

上图就是一个单链表的结构,plist是一个指向第一个节点的指针,往后看会发现,每一个节点都会包含了下一个节点的指针。

2.单链表的实现 

单链表的实现,我们依然通过三个文件来实现,为SList.h,SList.c和test.c

2.1 单链表的创建

我们通过前面顺序表就可以很快写出以下代码

typedef int SLDataType;
struct SListNode
{SLDataType data;struct SListNode* next;
}SList;

2.2 单链表的初始化

链表是由一个一个的节点组成的,所以我们要为节点申请空间,会用到malloc函数。

void test01()
{//手动初始化SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));node1->data = 1;SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));node2->data = 1;SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));node3->data = 1;SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));node4->data = 1;node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;SLTNode* plist = node1;
}

为了方便观察,我们先写一个打印链表的函数。

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

解释以上代码

pur是一个指向第一个节点的指针,我们知道最后一个节点中的指针为NULL,pur在不断的变换为next的值,直到pur的值为NULL就跳出循环。

f0d6b35670ad48bda8dd6cf097971e35.png

运行代码

714f26bb7f334b37a28066987501ba6a.png

我们就发现单链表初始化成功了。

但是上面的代码是我们动手来实现链表的初始化的,但这样写代码的效率就会降低,我们一般都会通过函数来实现,这就涉及到了链表数据的插入。

2.3 数据的插入

数据的插入方式我们分为尾插和头插的两种。

2.3.1 尾插

尾插,顾名思义就是从链表中的尾部插入一个新的节点。

c5cf0898e9de45b083801c6157360b55.png

上图就是尾插的形式。

思路分析

既然我们要插入一个新的节点,我们就要为新的节点申请空间,为了方便,我们同样把申请空间的操作包装成一个函数。

//申请空间
SLTNode* SLBuySpace(SLDataType x)
{SLTNode* new = (SLTNode*)malloc(sizeof(SLTNode));//判断空间是否申请成功if (new == NULL){perror("malloc fail");exit(1);//退出程序}//到这空间申请成功new->data = x;new->next = NULL;return new;
}

接着,既然要实现尾插,我们就要找到链表的尾巴,然后才能插上新的节点。

//找尾巴
SLTNode* ptail = *pphead;
while (ptail->next)
{ptail = ptail->next;
}
ptail->next = newnode;

尾插的总代码

//尾插
void SLPushBack(SLTNode** pphead, SLDataType x)
{assert(pphead);SLTNode* newnode = SLBuySpace(x);if (*pphead == NULL){//链表为空*pphead = newnode;}else{//链表不为空//找尾巴SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}}

需要注意的是,我们这里的形参是一个二级指针,因为我们要将原来phead指针指向的内容进行改变,如果我们单独将指针的值传过来,通过前面的学习,我们传值时,形参的改变是不会影响实参的,所以我们要将指针的地址传过来,通过地址修改实参的值。 

13f8f7097aaa4d2ab67af3c839aec2dc.png

运行代码

fc28d4e48bfc40d492402fa3b782493e.png

还需注意的是,我们要将链表为空和不为空分为两种情况处理,如果我们只考虑到链表不为空的情况,则当我们一开始处理的链表为空时,就不会进入循环,为空,ptial->next就无法进行解引用。 

2.3.1 头插 

头插,也就是将一个新的节点插入链表的头部,使新的节点称为第一个节点。

473e5bd44d8647b2b9bf9f5aad03b5b9.png

代码实现

//头插
void SLPushHead(SLTNode** pphead, SLDataType x)
{assert(pphead);//为新节点申请空间SLTNode* newnode = SLBuySpace(x);newnode->next = *pphead;*pphead = newnode;
}

头插的代码很简单,但是最后要让newnode成为新的phead,不要漏掉*pphead=newnode。

运行代码

b159301993f147448d07d6ae5585bdd4.png

2.4 数据的删除

2.4.1  尾删

尾删就是将链表中的最后一个节点删除掉。

思路分析

尾删我们就要找到链表的尾巴,并将其释放掉,但注意的是,当我们将最后一个节点释放掉之后,前一个节点中的next就会变成野指针,所以我们也要找到最后一个节点的前一个节点,并将其next指针赋值为NULL。

尾删前如下图

ffdee85191284d88ae196b6577c89b21.png

尾删后如下图

fbcd01006e1d4f818cae9ada4764556b.png

代码实现

void SLPopBack(SLTNode** pphead)
{assert(pphead && *pphead);//链表不能为空SLTNode* prev = *pphead;SLTNode* ptail = *pphead;while (ptail->next){prev = ptail;ptail = ptail->next;}//这里,prev和ptail找到free(ptail);prev->next = NULL;
}

运行代码

de8015dcc63d4089be289389215dc4e3.png

2.4.2 头删

 头删就是将链表中的第一个节点删点。

代码实现

//头删
void SLPopHead(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;//将下个节点变为新的节点
}

我们需要把下一个节点变为新的头节点,所以我们创建一个next先将下一个节点的地址存储起来。 

2.5 查找数据

查找数据很简单,只需遍历链表,并返回存储要查询数据节点的地址,如没由,就返回NULL。

代码实现

SLTNode* SLDataFind(SLTNode* phead, SLDataType x)
{assert(phead);SLTNode* pcur = phead;//遍历链表while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}

运行代码

e83a55ba8c2847a7a1ae0eaf47da26db.png

2.6  在指定位置之前插入数据

在指定位置之前插入数据,会影响到插入数据前一个节点的·指针,所以我们要找到插入位置的前一个节点。如下图

90db917b4b7e4ad39e02d43506eab8ea.png

代码实现

void SLAddPos(SLTNode** pphead, SLTNode* pos, SLDataType x)
{assert(pphead);assert(pos);SLTNode* newnode = SLBuySpace(x);if (*pphead == pos){//链表为空//头插SLPushHead(pphead, x);}else{//链表不为空//找位置pos前面的节点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}newnode->next = pos;prev->next = newnode;}	
}

2.7 在指定位置之后插入数据 

 在指定位置之后插入数据就很简单,这个操作会影响插入位置的后一个指针,因为我们可以通过插入位置来找到插入位置的后一个节点,不在需要遍历链表。

0f08b6b2f0f24a11892814e10541bfa0.png

我们只需将pos->next指向newnode,让newnode->next指向pos->next。

代码实现

void SLAddBack(SLTNode* pos, SLDataType x)
{assert(pos);//为插入节点申请空间SLTNode* newnode = SLBuySpace(x);SLTNode* next = pos->next;pos->next = newnode;newnode->next = next;
}

这里我们要先将pos->next保存下来,因为后面的pos->next会发生改变,而我们要让newnode->next指向原来的pos->next;

2.8 删除pos节点的数据 

当我们删除pos节点时,会影响到pos前后两个节点的指针,所以,我们首先要找到pos前后的两个节点。

4a2347ffd63c4bb682748191037eb922.png

代码实现

void SLErasePos(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);if (pos == *pphead){//只有一个节点//头删SLPopHead(pphead);}else{SLTNode* next = pos->next;//找prevSLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}free(pos);pos = NULL;prev->next = next;}}

注意事项:我们要先将pos->next的值先存储起来,因为前面的pos就会被释放掉了,最后就找不到pos->next了。

我们还要分情况讨论,当链表中只有一个节点时,那就是头删操作了,直接调用头删的函数就行了。

运行代码

7afd31b9624149cdab671233180cecb6.png

2.9 删除pos后的节点

思路分析

既然要删除pos后的节点,首先链表就不能为空,pos->next也不能为空。

0e995b8904064e49a68264cabefe57c6.png

注意,将pos后面的节点释放掉了之后,此时pos->next就是野指针来,要注意将pos->next置为空。

代码实现

void SLEraseAfter(SLTNode* pos) //pos->data==1
{assert(pos && pos->next);//要删除的节点SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

d309232e88b347fea5087b5a01da7435.png

2.10 销毁链表

销毁链表就一个一个销毁就行了。

代码实现

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

感谢观看。 

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

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

相关文章

[Diffusion Model笔记] DDPM数学推导版 2024.04.23

本文是观看以下视频的笔记: https://www.bilibili.com/video/BV1CU4y1i7jn/?p4&spm_id_frompageDriver 其他参考 https://zhuanlan.zhihu.com/p/614147698 https://zhuanlan.zhihu.com/p/563661713 这个写的非常详细: https://www.zhihu.com/ques…

成都爱尔胡建斌院长提醒视网膜脱离并非立即失明!这些征兆要注意!

大家都知道视网膜脱离危险,它的危险在于视网膜脱离后外层视网膜得不到脉络膜的血液供应,未及时复位视网膜感光细胞就会发生凋亡,视力发生缺损。 视网膜脱离对视力的影响是与病程同步的,发病初期眼前多有黑影、漂浮物、闪光感或幕…

十七、Java网络编程(一)

1、Java网络编程的基本概念 1)网络编程的概念 Java作为一种与平台无关的语言,从一出现就与网络有关及其密切的关系,因为Java写的程序可以在网络上直接运行,使用Java,只需编写简单的代码就能实现强大的网络功能。下面将介绍几个与Java网络编程有关的概念。 2)TCP/IP协议概…

2024年03月CCF-GESP编程能力等级认证Python编程三级真题解析

本文收录于专栏《Python等级认证CCF-GESP真题解析》,专栏总目录:点这里,订阅后可阅读专栏内所有文章。 一、单选题(共15题,共30分) 第1题 小杨的父母最近刚刚给他买了一块华为手表,他说手表上跑的是鸿蒙,这个鸿蒙是( ) A.小程序 B.计时器 C.操作系统 D.神话人物 答…

江苏开放大学2024年春《机械CAD/CAM 050097》第三次在线作业参考答案

单选题 1数控编程技术包含了数控加工与编程、金属加工工艺、CA/CA软件操作等多方面的知识,其主要任务就是计算加工走刀中的__________。 A刀具终点 B刀位点 C刀具起点 D刀具中点 答案是:B 2操作人员在CAD/CA系统中起____________作用。 …

认识HTTP

HTTP缺点 通信使用明文(不加密),内容可能会被窃听 不验证通信方的身份,可能遭遇伪装 无法证明报文的完整性,所以有可能遭篡改 一、通信使用明文(不加密),内容可能会被窃听 TCP/…

互联网安全面临的全新挑战

前言 当前移动互联网安全形势严峻,移动智能终端漏洞居高不下、修复缓慢,移动互联网恶意程序持续增长,同时影响个人和企业安全。与此同时,根据政策形势移动互联网安全监管重心从事前向事中事后转移,需加强网络安全态势感…

new String和直接赋值的一些问题

分析1 我们先看以下代码: String str1 "abc"; // 在常量池中String str2 new String("abc"); // 在堆上System.out.println(str1 str2)以上结果的输出是什么? 输出:false 前置知识: 在JVM中&#xff0c…

快手面试算法真题

按照html中的标签层数遍历节点名。 例如&#xff1a;html代码如下&#xff1a;(上面的数字表示层数) <!-- 1 --><div class"div1"><!-- 2 --><span class"span1"></span><!-- 2 --><p class"p1"><…

PTA 天梯赛 L1-010 比较大小【C++】 L1-011 A-B 【C++ vector动态数组】【Python 字符串replace函数】

L1-010 比较大小 判断顺序很重要 #include<iostream> using namespace std; int main() {int a, b, c;cin >> a >> b >> c;int temp;if (a > b) {temp a;a b;b temp;}if (a > c) {temp a;a c;c temp;}if (b > c) {temp b;b c;c te…

x86 64位的ubuntu环境下汇编(无优化)及函数调用栈的详解

1. 引言 为了深入理解c&#xff0c;决定学习一些简单的汇编语言。使用ubuntu系统下g很容易将一个c的文件编译成汇编语言。本文使用此方法&#xff0c;对一个简单的c文件编译成汇编语言进行理解。 2.示例 文件名&#xff1a;reorder_demo.cpp #include<stdio.h>typede…

常见的工业路由器访问问题

A&#xff1a;工业路由器已经设置了pptp怎么访问路由下面的电脑 1. 确认PPTP VPN设置&#xff1a;首先&#xff0c;确保PPTP VPN服务器在工业路由器上已正确设置&#xff0c;并且处于活动状态。这包括确保VPN服务器的IP地址、端口、用户名和密码等设置正确无误。 2. 连接到VP…