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.mzph.cn/news/803078.shtml

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

相关文章

基于springboot+vue+Mysql的学习平台

开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:…

Centos 下载地址

下载镜像地址: 1、官网地址:The CentOS Project 2、阿里镜像站:centos安装包下载_开源镜像站-阿里云 3、清华镜像源:Index of /centos/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 3.、CentOS搜狐镜像&#xff1…

Spark-Scala语言实战(13)

在之前的文章中,我们学习了如何在spark中使用键值对中的keys和values,reduceByKey,groupByKey三种方法。想了解的朋友可以查看这篇文章。同时,希望我的文章能帮助到你,如果觉得我的文章写的不错,请留下你宝贵的点赞,谢…

c++类静态成员变量

C中的静态成员变量是属于类的成员变量&#xff0c;而不是属于类的具体实例的。静态成员变量提供了一种方便的机制&#xff0c;可以在类的所有实例之间共享数据&#xff0c;并且可以用于跟踪类的实例数量、存储全局信息以及实现全局性质的函数或数据。 #include <iostream&g…

JavaSE:图书管理系统

目录 一、前言 二、内容需求 三、类的设计 &#xff08;一&#xff09;图书类 1.Book 类 2.BookList 类 &#xff08;二&#xff09;操作类 1.添加图书AddOperation类 2.借阅图书BorrowOperation类 3.删除图书DelOperation类 4.显示图书ShowOperation类 5.退出系统Ex…

通俗易懂地解释Go语言不同版本中垃圾回收机制的演进过程

完整课程请点击以下链接 Go 语言项目开发实战_Go_实战_项目开发_孔令飞_Commit 规范_最佳实践_企业应用代码-极客时间 Go 1.3时代 - 标记清除算法 这就像一个人要打扫房间,首先需要暂停其他活动。然后开始查看房间里的每件物品,对于自己仍需要使用的物品做上记号。查看完毕后…

【三十六】【算法分析与设计】综合练习(3),39. 组合总和,784. 字母大小写全排列,526. 优美的排列

目录 39. 组合总和 对每一个位置进行枚举 枚举每一个数出现的次数 784. 字母大小写全排列 526. 优美的排列 结尾 39. 组合总和 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不…

手写Spring框架

手写Spring框架 准备工作Spring启动和扫描逻辑实现依赖注入的实现Aware回调模拟实现和初始化机制模拟实现BeanPostProcessor (Bean的后置处理器) 模拟实现Spring AOP 模拟实现Spring Bean生命周期源码分析 Spring中两种生成代理的方式题外话 Spring事务相关Spring事务传播机制S…

【Java】用吃拉面的方式打开Java程序的执行流程

你还在为记不住Java程序的执行流程而苦恼吗&#xff1f;那你来对地方了&#xff0c;这篇博客将让你一次记住终生不忘。 接下来&#xff0c;我们用去兰州拉面的过程&#xff0c;来形象地看一下&#xff0c;当你运行一个的main方法时&#xff0c;到底发生了什么&#xff1a; 走进…

c++找最高成绩

根据给定的程序&#xff0c;写成相关的成员函数&#xff0c;完成指定功能。 函数接口定义&#xff1a; 定义max函数&#xff0c;实现输出最高成绩对应的学号以及最高成绩值。 裁判测试程序样例&#xff1a; #include <iostream> using namespace std; class Student{…

C++——栈和队列容器

前言&#xff1a;这篇文章我们将栈和队列两个容器放在一起进行分享&#xff0c;因为这两个要分享的知识较少&#xff0c;而且两者在结构上有很多相似之处&#xff0c;比如栈只能在栈顶操作&#xff0c;队列只能在队头和队尾操作。 不同于前边所分享的三种容器&#xff0c;这篇…

HarmonyOS 应用开发-ArkUI(ets)仿“腾讯新闻”APP

一、效果演示 1、新闻列表页 2、新闻详情页、图片展示页 3、视频页 4、动态页 二、 流程图 –本来自定义了视频的控制栏的&#xff0c;但是发现VideoController()控制器的bug会导致控制器失效&#xff0c;所以没继续做。视频页先不搞了。 三、文件组织&#xff08;“我的页面…

网工内推 | 深信服、宁德时代,最高20K招安全工程师,包吃包住

01 深信服科技 招聘岗位&#xff1a;安全服务工程师 职责描述&#xff1a; 1.负责现场安全服务项目工作内容&#xff0c;包含渗透测试、安全扫描、基线核查、应急响应等&#xff1b; 2.协助用户完成安全测试漏洞整改、复测工作&#xff1b; 3.为用户提供网络、主机、业务系统等…

dg_mmld部分复现

Ours ( K ˆ \^{K} Kˆ2)复现结果– Photo&#xff1a;0.9634730538922156 (at Epoch 23) Art&#xff1a;0.8125 (at Epoch 23) Cartoon&#xff1a;0.7713310580204779 (at Epoch 18) 差距在可接受范围内 辅助信息 If you send 作者 an e-mail, 作者 will tell you a URL w…

2022年蓝桥杯省赛——重合次数

目录 题目链接&#xff1a;1.重合次数 - 蓝桥云课 (lanqiao.cn) 题目描述 答案提交 运行限制 思路 总结 题目链接&#xff1a;1.重合次数 - 蓝桥云课 (lanqiao.cn) 题目描述 在同一天中, 从上午 6 点 13 分 22 秒到下午 14 点 36 分 20 秒, 钟表上的 分针和秒针一共重合…

HTML - 请你谈一谈img标签图片和background背景图片的区别

难度级别:中级及以上 提问概率:65% 面试官当然不会问如何使用img标签或者background来加载一张图片,这些知识点都很基础,相信只要从事前端开发一小段时间以后,就可以轻松搞定加载图片的问题。但很多人习惯用img标签,很多人习惯用backgro…

Java 数据类型转换

String 转 char 数组 String str "abc"; char[] charArr str.toCharArray();char 数组转 String char[] charArr{a, b, c}; String str new String(charArr);char 字符转 String 使用 String.valueOf() 方法 char ch a; String str String.valueOf(ch);使…

element-ui的年份范围选择器,选择的年份需等于或小于当前年份,选择的年份范围必须在三年之内

写在前面 日期限制处理&#xff08;禁用&#xff09;&#xff0c;下面我以我这边的需求为例&#xff0c; 选择的年份需等于或小于当前年份 选择的年份范围必须在三年之内 1.限制起始日期小于截止日期 1&#xff09;根据用户选中的开始日期&#xff0c;置灰不可选的日期范围&…

【腾讯云 TDSQL-C Serverless 产品体验】饮水机式使用云数据库

云计算的发展从IaaS&#xff0c;PaaS&#xff0c;SaaS&#xff0c;到最新的BaaS&#xff0c;FasS&#xff0c;在这个趋势中serverless(去服务器化&#xff09; 计算资源发展Physical -> Virtualisation -> Cloud Compute -> Container -> Serverless。 一、背景介绍…

LeetCode 264. 丑数 II

解题思路 三个指针进行遍历&#xff0c;归并排序。 相关代码 class Solution {public int nthUglyNumber(int n) {int temp1;int a[] new int[n];a[0] 1;for(int i0,j0,k0,index1;index<n;index){temp Math.min(a[i]*2,Math.min(a[k]*5,a[j]*3));a[index] temp;if(te…