C语言单链表

1. 单链表的概念和结构

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

链表与顺序表都属于线性表,顺序表在物理存储结构上是线性的,但是链表在物理存储结构上却是非线性的,它是由一个个的结点所构成。

一个个结点通过地址链接在一起,所以这种逻辑上的线性存储结构就叫做链表。

准确来说,结点之间只有逻辑上的关系,链表只是假想出来的对这些相互之间有联系的结点的统称。

单链表的“单”意味着每个结点除了存储自己的要存储的数据之外,就只存储了另外一个结点的地址(其逻辑上,下一个结点的地址)。

每个结点并没有自己的名字或直接被查找到的方式,他们只能被上一个元素存储的指针所找到。

其结构体定义如下:

typedef int SLTDataType;//要存储的数据的类型,此处是inttypedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;

其中,data表示要存的数据,next是指向下一个结点的指针。 

在之后的学习中,我们还会遇到双向链表,循环链表,十字链表等。

稀疏矩阵的链式存储结构:十字链表-CSDN博客

既然这些结点在逻辑上被视作一个整体,那么要如何表示这个整体呢?

由于每个结点中存储了下一个结点的地址,所以我们只要找到第一个结点,就可以找到之后所有被链接起来的结点。

由于这些结点并没有自己的名字,所以我们会单独定义一个头结点指针plist来指向第一个结点。

这样,通过plist我们就可以遍历整个链表,通常就将plist当作链表这个整体。

这个其实不难理解,类比数组即可:

数组名既是首元素地址,又代表了整个数组;plist既是首元素地址,又代表了整个链表。

2. 对单链表进行操作用到的函数声明

//打印
void SLTPrint(SLTNode* phead);
//尾插
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);

其中,打印链表的函数需要根据具体情况来实现,打印完一个结点的数据再打印下一个,直到某个元素的next指针为NULL为止,本文对这个函数不做过多解释。

由于每个插入函数在插入结点时都需要申请一个新的结点来插入,所以除了上面这些函数,我们在实现链表的文件中还会额外定义一个函数来进行结点的申请:

//创建新结点
SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}

3. 插入函数

3.1 尾插

申请新结点,找到链表中的尾结点,使尾结点的next指针指向新的结点即可。

注意,当我们的头结点指针为NULL时(链表中没有元素),我们需要让头结点指向新申请的结点,此时需要修改头结点指针的值。

所以,我们在传入数据时,传入的是头结点指针的地址。

在函数中,要修改实参的值就需要传入实参的地址。

我们要修改头结点指针的值就要传入头结点指针的地址,即一个二级指针。

接下来的,传入了二级指针的函数也是由于在某些情况下需要更改头结点指针的值。 

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

3.2 头插

申请新的结点,然后将链表原本的头结点链接在新结点之后,再让头结点指针指向新结点即可。

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

3.3 在指定位置之前插入

在指定位置之前插入,需要使该位置之前原本的结点的next指针指向新结点,再让新结点的next指针指向该位置的结点。

由于在单链表中,我们只能查找到某个结点的下一个结点,所以,在某个结点之前插入就需要遍历链表,来找到该位置之前的那个结点。

这也是单链表的一个缺陷。

在某个位置前插入,那么链表中一定要有结点才能插入,所以pphead和*pphead都不能为NULL。

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

3.4 在指定位置之后插入

先让该位置的下一个结点接在新结点之后,再让新结点接在该位置之后。

注意,这两步次序不能交换,如果先进行第二步,则不能通过该位置结点的next指针找到原本的下一个结点。

//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}

4. 删除函数

4.1 尾删

找到尾结点,再将其释放即可,当然,在释放掉尾结点之后,需要将前一个结点的next指针置为NULL。

但我们无法通过尾结点来找到前一个结点,所以每次让ptail指针指向下一个结点之前,可以另外设置一个prev指针来记录下当前的结点。

也可以采取如下的写法,直接找尾结点的前一个结点。

当链表中只有一个结点时,上述的两种写法都会导致解引用NULL的错误,所以我们需要单独处理一下这种情况。

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

4.2 头删

释放掉头结点,然后让头结点指针指向第二个结点即可。

但是,释放掉头结点会导致我找不到第二个结点,所以我们定义了一个newphead指针来指向第二个结点。

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

4.3 删除指定位置的结点

与尾删类似,只不过要找的是某个指定的结点而不是尾结点,同样需要遍历链表。

//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);}
}

4.4 删除指定位置之后的结点

释放掉该位置之后的结点,然后将之后剩余的结点接到该位置之后即可。

要注意的与头删类似。

//删除指定位置之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

5. 查找结点

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

6. 销毁单链表

依次删除每个结点即可。

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

7. 结语

单链表相对于顺序表来说,优势有:

1. 对数据进行增加删除较为方便,因为我只需要改变结点之间的链接关系即可。

2. 不会浪费空间,有需要我才会开辟新的结点(尽管每个结点都是结构体,所占空间比数组元素多)。

劣势:

1. 查找不便,只能一个一个地通过next指针来查找,且无法访问某结点的前一个结点。而顺序表则可通过下标访问操作符来对数据进行直接访问。 

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

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

相关文章

P1123 取数游戏(dfs算法)

题目描述 一个 NM 的由非负整数构成的数字矩阵,你需要在其中取出若干个数字,使得取出的任意两个数字不相邻(若一个数字在另外一个数字相邻 8个格子中的一个即认为这两个数字相邻),求取出数字和最大是多少。 输入格式 第…

蝙蝠优化算法(bat optimization algorithm)

注意:本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 ([www.aideeplearning.cn]) 算法背景 蝙蝠优化算法(Bat Algorithm)是一种基于群体智能的优化算法,它的灵感来源于蝙蝠捕食时的回声定位行…

Python3 replace()函数使用详解:字符串的艺术转换

博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通鸿蒙》 …

[从0开始AIGC][Transformer相关]:算法的时间和空间复杂度

一、算法的时间和空间复杂度 文章目录 一、算法的时间和空间复杂度1、时间复杂度2、空间复杂度 二、Transformer的时间复杂度分析1、 self-attention 的时间复杂度2、 多头注意力机制的时间复杂度 三、transformer的空间复杂度 算法是指用来操作数据、解决程序问题的一组方法。…

人工智能应用工程师特训营丨国家认证,行业必备

人工智能应用工程特训营 提升目标: 1、提高专业认可度,增强职场竞争力 2、实战项目驱动,提升应用能力 3、技术体系全面,涵盖多个领域 4、实时在线答疑,强化学习互动 特训营学习流程: 职业技术证书&#xff…

【鸿蒙开发】系统组件Text,Span

Text组件 Text显示一段文本 接口: Text(content?: string | Resource) 参数: 参数名 参数类型 必填 参数描述 content string | Resource 否 文本内容。包含子组件Span时不生效,显示Span内容,并且此时text组件的样式不…

数学基础:常见函数图像

来自: https://www.desmos.com/calculator/l3u8133jwj?langzh-CN 推荐: https://www.shuxuele.com/index.html 一、三角函数 1.1 正弦 sin(x) 1.2 余弦 cos(x) 1.3 正切 tan(x) 1.4 余切 cot(x) 1.5 正弦余弦综合 1.6 正切余切综合 二、指数对数

Linux内核自带的LED驱动实验:Led驱动功能测试

一. 简介 前面几篇文章学习了如何使用Linux内核自带的Led驱动。一篇文章通过对驱动分析,了解了驱动与设备匹配的关键点。 一篇文章学习了如何配置使能Linux内核自带的Led驱动,第二篇文章学习创建Led设备树节点(针对使用Linux内核自带的Led…

HJ37 统计每个月兔子的总数(动态规划)

高端的食材,往往只需要最简单的烹饪方法。 import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的…

小米汽车:搅动市场的鲶鱼or价格战砧板上的鱼肉?

3月28日晚,备受关注的小米汽车上市发布会召开,小米集团董事长雷军宣布小米SU7正式发布。小米汽车在带飞股价的同时,二轮订购迅速售尽。 图一:小米集团股价 雷军口中“小米汽车迈出的第一步,也是人生最后一战的开篇”&a…

Spring Cloud微服务入门(五)

Sentinel的安装与使用 安装部署Sentinel 下载Sentinel: https://github.com/alibaba/Sentinel/releases Sentinel控制台 https://localhost:8080 用户和密码为sentinel 使用Sentinel 加依赖: 写配置: 输入: java -Dserver.po…

《QT实用小工具·二十二》多种样式导航按钮控件

1、概述 源码放在文章末尾 该项目实现了多种样式的导航按钮控件 可设置文字的左侧、右侧、顶部、底部间隔。 可设置文字对齐方式。 可设置显示倒三角、倒三角边长、倒三角位置、倒三角颜色。 可设置显示图标、图标间隔、图标尺寸、正常状态图标、悬停状态图标、选中状态图标…

基于spring boot的漫画之家系统

基于spring boot的漫画之家系统设计与实现 开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7(一定要5.7版本) 数据库工具:Navicat11 开发软件&…

程序语言基础

根据希赛相关视频课程汇总整理而成,个人笔记,仅供参考。考点偏向于通用程序语言的基础概念。 程序语言基础概念 程序设计语言: ①低级语言 机器语言汇编语言 汇编语言:指令语句/伪指令语句/宏指令语句 ②高级语言 Fotrane语言&…

vscode连接远程服务器一直需要输密码,但是连不上

问题:vscode连接远程服务器一直需要输密码,但是连不上。 解决办法:kill 掉该远程服务器,然后再重新连接 操作: windows: ctrlshiftp mac:cmdshiftp 调出指令,然后选择“Remote SSH:Kill Vscode Serve…

强化学习MPC——(二)

本篇主要介绍马尔科夫决策(MDP)过程,在介绍MDP之前,还需要对MP,MRP过程进行分析。 什么是马尔科夫,说白了就是带遗忘性质,下一个状态S_t1仅与当前状态有关,而与之前的状态无关。 为…

【鸿蒙开发】系统组件Row

Row组件 Row沿水平方向布局容器 接口: Row(value?:{space?: number | string }) 参数: 参数名 参数类型 必填 参数描述 space string | number 否 横向布局元素间距。 从API version 9开始,space为负数或者justifyContent设置为…

关于51单片机TMOD定时器的安全配置

定时器介绍: -------------------------------------------------------------------------------------------------------------------------- 首先配置的是控制寄存器 TCON 说直白点,这个寄存器就是用来计数的,打开计时器,关…

分布式锁的原子性问题

4.6 分布式锁的原子性问题 更为极端的误删逻辑说明: 线程1现在持有锁之后,在执行业务逻辑过程中,他正准备删除锁,而且已经走到了条件判断的过程中,比如他已经拿到了当前这把锁确实是属于他自己的,正准备删…

本地代码第一次提交到远程仓库gitee

1.在gitee新建仓库 2.新建一个空文件夹 打开黑窗口,执行命令 克隆仓库地址 执行命令 git clone https://gitee.com/llncomms/test.git打开隐藏的项目 复制全部内容到需要提交的代码中 3.在提交的代码中执行命令 $ git add .git commit -m 首次提交$ git push提交成功