数据结构--单链表实现

                                                        欢迎光顾我的homepage

前言        

        链表和顺序表都是线性表的一种,但是顺序表在物理结构和逻辑结构上都是连续的,但链表在逻辑结构上是连续的,而在物理结构上不一定连续;来看以下图片来认识链表与顺序表的差别

这里以动态顺序表为例,和链表中的单链表对比一下

动态顺序表

单链表

        这里就可以很清晰的看到顺序表的底层其实就是一个数组,数据的是连续存储的(顺序表物理结构连续);而链表它每一个数据都不是连续的(链表物理结构上不一定连续)。

链表节点

        通过观察上图,我们会发现链表每一个节点都存放在数据和下一个节点的地址。

        那么来想一下,为了链表每一个节点都统一起来,都存储有效数据和下一个节点的地址,我们就需要创建应该结构体,来存储有效数据和下一个节点的指针;
注:这里只是单链表

typedef int SLType;
typedef struct SLTNode
{SLType data;struct SLTNode* next;
}SLT;

创建好链表节点,接下来就来实习单链表存储数据的这些功能。

单链表实现

先来看一下单链表都实现都哪些功能

//输出链表
void SLTPrint(SLT* phead);
//创建节点
SLT* SLTCreat(SLType x);
//单链表头插
void SLTPushFront(SLT** pphead, SLType x);
//单链表尾插
void SLTPushBack(SLT** pphead, SLType x);
//单链表头删
void SLTPopFront(SLT** pphead);
//单链表尾删
void SLTPopBack(SLT** pphead);
//查找数据
SLT* SLTFind(SLT* phead, SLType x);
//指定位置之前插入
void SLTInsert(SLT** pphead, SLT* pos, SLType x);
//指定位置之后插入
void SLTInsertAfter(SLT* pos, SLType x);
//删除指定节点
void SLTErase(SLT** pphead, SLT* pos);
//删除指定位置后一个节点
void SLTEraseAfter(SLT* pos);

创建节点

        这里创建节点,还是使用动态内存来创建,创建完成后,将数据存储进去,并把新节点的下一个节点置为NULL

代码如下:

//创建节点
SLT* SLTCreat(SLType x)
{SLT* newnode = (SLT*)malloc(sizeof(SLT));assert(newnode);newnode->data = x;newnode->next = NULL;return newnode;
}

        测试一下代码是否正确

可以看到代码没有问题。

输出链表

        由于这里实现单链表,存储的是整型数据,就以整型的方式输出,若存储其他类型的数据,就以存储类型的方式输出。

输出链表,首先就要遍历链表,因为链表最后一个节点里存储的下一个节点的地址为空(即最后一个节点  ->next 为空)所以,遍历链表结束的条件就是ptail ==NULL,没输出一个就让ptail指向下一个节点,直到为空,遍历结束。

        来写代码实现:

//输出链表
void SLTPrint(SLT* phead)
{SLT* ptail = phead;while (ptail!= NULL)//也可以直接写成 ptail{printf("%d -> ", ptail->data);ptail = ptail->next;}printf("NULL\n");
}

这里先创建两个节点并存储数据输出看一下结果

int main()
{SLT* plist = SLTCreat(1);plist->next = SLTCreat(2);SLTPrint(plist);return 0;
}

        这里也成功输出插入的两个数据。(最后一个节点的next指向空)

单链表头插

        在链表头部插入数据,不用像顺序表那样去移动所以的有效数据,链表只需要改变一个指针的指向即可

假设现在链表中已经存在两个数据,再进行头插,这时就只需要改变plist的指向即可,当然新节点的next指针也要指向下一个节点(plist指向的节点)。

代码如下:

//单链表头插
void SLTPushFront(SLT** pphead, SLType x)
{assert(pphead);SLT* newnode = SLTCreat(x);newnode->next = *pphead;*pphead = newnode;
}

还有一种情况,如果现在链表中没有数据,再进行头插,这里代码也能实现链表没有数据时的头插

单链表尾插

        链表的尾插,因为指针传的是指向头节点的指针的地址,所以,我们需要先遍历链表,找到链表的尾部,再修改尾节点的next指针指向。

假设现在链表中已经存在两个数据,再进行尾插,此时我们只需要找到链表的尾部,修改尾节点next指针指向即可,代码如下

//单链表尾插
void SLTPushBack(SLT** pphead, SLType x)
{assert(pphead);SLT* newnode = SLTCreat(x);SLT* ptail = *pphead;//遍历链表while (ptail->next){ptail = ptail->next;}ptail->next = newnode;
}

        考虑了这种情况,再来看以下链表为空的情况,如果链表为空,这里ptail->next就对空指针进行解引用操作了;所以,我们需要判断链表是否为空?如果为空,插入的新节点就是头节点,直接让plist(即*pphead)指向新节点即可;如果不为空就正常进行尾插。

最终代码如下:

//单链表尾插
void SLTPushBack(SLT** pphead, SLType x)
{	assert(pphead);SLT* newnode = SLTCreat(x);if (*pphead == NULL) //判断链表是否为空{*pphead = newnode;}else {SLT* ptail = *pphead;//遍历链表while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}
}

这里代码可以正常进行尾插。

单链表头删

        链表头删,首先我们要判断链表是否为空,如果为空(空链表没有数据该如何删除呢?

链表头删,我们需要修改plist(*pphead)指向链表的下一个节点即头节点的next指针。

        此外:因为我们的节点都是动态申请的内存,所以在删除时,需要把它释放掉。

思路很简单,写一下代码:
 

//单链表头删
void SLTPopFront(SLT** pphead)
{assert(pphead && *pphead);SLT* del = (*pphead);*pphead = (*pphead)->next;free(del);del = NULL;
}

再来看一个如果链表为空,又是啥结果呢?

现在链表已经为空,在执行一次头删代码

这里assert断言报错。

单链表尾删

        首先尾删与头删一样,链表不能为空。

        尾删与尾插也有共同之处,就是遍历链表,找到链表的尾节点。找到尾节点,删除尾节点;然后修改尾节点上一个节点的next指针为NULL;所以在遍历链表时就要多记录一个节点。

//单链表尾删
void SLTPopBack(SLT** pphead)
{assert(pphead && *pphead);SLT* ptail = *pphead;SLT* pcur = *pphead;//遍历链表while (ptail->next){pcur = ptail;ptail = ptail->next;}pcur->next = NULL;free(ptail);ptail  = NULL;
}

        在测试的时候我们会发现一个问题,如果链表只有一个节点,删除之后我们的plist指针并没有置为空,而我们的空间已经释放掉了,这是很危险的;所以这里我们先判断一下链表是否只有一个节点,如果是,我们释放完空间以后,将(*pphead)置为空,以免出现野指针的情况。

完善后代码:

//单链表尾删
void SLTPopBack(SLT** pphead)
{assert(pphead && *pphead);if ((*pphead)->next== NULL){free(*pphead);*pphead = NULL;}else {SLT* ptail = *pphead;SLT* pcur = *pphead;//遍历链表while (ptail->next){pcur = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;pcur->next = NULL;}
}

测试没有问题,代码能够完成尾删这个功能。

查找数据

        查找数据,遍历链表查找数据,如果找到就返回数据所在节点的地址,如果没有找到就返回NULL;

//查找数据
SLT* SLTFind(SLT* phead, SLType x)
{SLT* ptail = phead;while (ptail){if (ptail->data == x){return ptail;}ptail = ptail->next;}return NULL;
}

这里测试以下:

数据存在时

数据不存在时

指定节点之前插入

        在链表指定节点之前插入数据,我们还需要知道指定节点的前一个节点,所以仍然需要遍历链表,寻找指定节点pos前一个节点。

//指定位置之前插入
void SLTInsert(SLT** pphead, SLT* pos, SLType x)
{assert(pphead && *pphead);SLT* ptail = *pphead;if (ptail == pos)//头插{SLTPushFront(pphead, x);}else{SLT* newnode = SLTCreat(x);while (ptail->next != pos)//找到pos位置{ptail = ptail->next;}newnode->next = pos;ptail->next = newnode;}
}

当然,这里如果故意传NULL给pos,(这就没啥意义了)这里也可以写以下assert断言pos。

指定节点之后插入

        在指定节点之后插入数据,就不需要再进行遍历链表,这里直接插入即可。

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

删除指定节点

        删除链表中的指定节点,我们需要这个节点的上一个节点,所以又需要遍历链表,找到pos上一个节点,修改pos->next指针指向。

代码如下:

//删除指定节点
void SLTErase(SLT** pphead, SLT* pos)
{//找到pos上一个节点SLT* ptail = *pphead;while (ptail->next != pos){ptail = ptail->next;}SLT* p = pos->next;free(pos);pos = NULL;ptail->next = p;
}

删除指定节点后一个节点

        删除链表指定节点后一个节点,因为pos就是删除节点的上一个节点,所以不需要遍历链表,直接删除即可。

//删除指定位置后一个节点
void SLTEraseAfter(SLT* pos)
{assert(pos->next);SLT* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}

这里如果传过来的就是链表的尾节点,那没办法删除后一个节点,所以断言pos->next;

代码总览

#include"SList.h"
//创建节点
SLT* SLTCreat(SLType x)
{SLT* newnode = (SLT*)malloc(sizeof(SLT));assert(newnode);newnode->data = x;newnode->next = NULL;return newnode;
}
//输出链表
void SLTPrint(SLT* phead)
{SLT* ptail = phead;while (ptail != NULL)//也可以直接写成 ptail{printf("%d -> ", ptail->data);ptail = ptail->next;}printf("NULL\n");
}
//单链表头插
void SLTPushFront(SLT** pphead, SLType x)
{assert(pphead);SLT* newnode = SLTCreat(x);newnode->next = *pphead;*pphead = newnode;
}
//单链表尾插
void SLTPushBack(SLT** pphead, SLType x)
{	assert(pphead);SLT* newnode = SLTCreat(x);if (*pphead == NULL) //判断链表是否为空{*pphead = newnode;}else {SLT* ptail = *pphead;//遍历链表while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}
}
//单链表头删
void SLTPopFront(SLT** pphead)
{assert(pphead && *pphead);SLT* del = (*pphead);*pphead = (*pphead)->next;free(del);del = NULL;
}
//单链表尾删
void SLTPopBack(SLT** pphead)
{assert(pphead && *pphead);if ((*pphead)->next== NULL){free(*pphead);*pphead = NULL;}else {SLT* ptail = *pphead;SLT* pcur = *pphead;//遍历链表while (ptail->next){pcur = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;pcur->next = NULL;}
}
//查找数据
SLT* SLTFind(SLT* phead, SLType x)
{SLT* ptail = phead;while (ptail){if (ptail->data == x){return ptail;}ptail = ptail->next;}return NULL;
}
//指定位置之前插入
void SLTInsert(SLT** pphead, SLT* pos, SLType x)
{assert(pphead && *pphead);SLT* ptail = *pphead;if (ptail == pos)//头插{SLTPushFront(pphead, x);}else{SLT* newnode = SLTCreat(x);while (ptail->next != pos)//找到pos位置{ptail = ptail->next;}newnode->next = pos;ptail->next = newnode;}
}
//指定位置之后插入
void SLTInsertAfter(SLT* pos, SLType x)
{assert(pos);SLT* newnode = SLTCreat(x);newnode->next = pos->next;pos->next = newnode;
}
//删除指定节点
void SLTErase(SLT** pphead, SLT* pos)
{//找到pos上一个节点SLT* ptail = *pphead;while (ptail->next != pos){ptail = ptail->next;}SLT* p = pos->next;free(pos);pos = NULL;ptail->next = p;
}
//删除指定位置后一个节点
void SLTEraseAfter(SLT* pos)
{assert(pos->next);SLT* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}

感谢各位大佬支持并指出问题,

如果本篇内容对你有帮助,可以一键三连支持以下,感谢支持!!!

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

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

相关文章

深度解析Java世界中的对象镜像:浅拷贝与深拷贝的奥秘与应用

在Java编程的浩瀚宇宙中,对象拷贝是一项既基础又至关重要的技术。它直接关系到程序的性能、资源管理及数据安全性。然而,提及对象拷贝,不得不深入探讨其两大核心类型:浅拷贝(Shallow Copy)与深拷贝&#xf…

CAN转PN网关模块连接激光切割机的配置方法

激光切割机在工业生产中被广泛应用,而激光发射器与控制设备常以不同的协议存在两者之间,CAN总线和Profinet以各自的特点被广泛用于设备当中。本文将介绍介绍兴达易控CAN转Profinet网关模块(XD-PN_CAN20)连接 CAN 激光切割机的使用…

WGAN(Wassertein GAN)

WGAN E x ∼ P g [ log ⁡ ( 1 − D ( x ) ) ] E x ∼ P g [ − log ⁡ D ( x ) ] \begin{aligned} & \mathbb{E}_{x \sim P_g}[\log (1-D(x))] \\ & \mathbb{E}_{x \sim P_g}[-\log D(x)] \end{aligned} ​Ex∼Pg​​[log(1−D(x))]Ex∼Pg​​[−logD(x)]​ 原始 GAN …

Python爬虫教程第1篇-基础知识

文章目录 什么是爬虫爬虫的工作原理用途搜索引擎爬虫Robots协议HTTP的请求过程URL的含义HTTP常见请求头爬虫常用的技术 什么是爬虫 信息的交互是通过web网页、或者移动端等不同的客户端端形式进行交互,这个过程是一个人与网路正常的交互行为。而爬虫可以用来模拟人…

SwiftUI 6.0(iOS 18.0)滚动视图新增的滚动阶段(Scroll Phase)监听功能趣谈

何曾几时,在 SwiftUI 开发中的秃头小码农们迫切需要一种能够读取当前滚动状态的方法。 在过去,他们往往需要借助于 UIKit 的神秘力量。不过这一切在 SwiftUI 6.0 中已成“沧海桑田”。 在本篇博文中,您将学到如下内容: 1. Scroll…

植物大战僵尸融合版最新版1.0下载及安装教程

《植物大战僵尸融合版》最新版1.0已经发布,为粉丝们带来了全新的游戏体验。这个版本由B站UP主蓝飘飘fly精心打造,引入了创新的植物融合玩法,让玩家可以享受策略和创意的结合。以下是游戏的详细介绍和安装指南: 游戏特色介绍 全新…

审核平台前端新老仓库迁移

背景 审核平台接入50业务,提供在线审核及离线质检、新人培训等核心能力,同时提供数据报表、资源追踪、知识库等工具。随着平台的飞速发展,越来越多的新业务正在或即将接入审核平台,日均页面浏览量为百万级别。如今审核平台已是公司…

可验证算法在招投标领域的专家“盲抽”中的标段识别码加密应用研究

摘要 在招投标过程中,标段(包)识别码的安全性至关重要。本文提出了一种基于可验证算法的标段识别码加密方法,以确保其在专家“盲抽”过程中的保密性和可信性。通过对不同表的标段识别码进行全量加密,并通过匹配验证其…

【Nginx】docker运行Nginx及配置

Nginx镜像的获取 直接从Docker Hub拉取Nginx镜像通过Dockerfile构建Nginx镜像后拉取 二者区别 主要区别在于定制化程度和构建过程的控制: 直接拉取Nginx镜像: 简便性:直接使用docker pull nginx命令可以快速拉取官方的Nginx镜像。这个过程…

SpringBoot 启动流程四

SpringBoot启动流程四 前面这个创建对象是初始化SpringApplication对象 是加载了SpringBoot程序的所有相关配置 我们接下来要将这个run方法 run过程是一个运行 初始化容器 我们看我们的运行结果是得到一个ConfigurableApplicationContext对象 package com.bigdata1421.star…

【Android】Android基础--显式Intent和隐式Intent

人不走空 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨 目录 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌…

STM32和DHT11使用显示温湿度度(代码理解)+单总线协议

基于STM32CT,利用DHT11采集温湿度数据,在OLED上显示。一定要阅读DHT11数据手册。 1、 DHT11温湿度传感器 引脚说明 1、VDD 供电3.3~5.5V DC 2、DATA 串行数据,单总线 3、NC 空脚 4、GND 接地,电源负极 硬件电路 微…

【Linux进程】进程优先级 Linux 2.6内核进程的调度

前言 进程是资源分配的基本单位, 在OS中存在这很多的进程, 那么就必然存在着资源竞争的问题, 操作系统是如何进行资源分配的? 对于多个进程同时运行, 操作系统又是如何调度达到并发呢? 本文将以Linux kernel 2.6为例 , 向大家介绍进程在操作系统中 (OS) 的调度原理; 1. 进程优…

嵌入式linux面试1

1. linux 1.1. Window系统和Linux系统的区别 linux区分大小写windows在dos(磁盘操作系统)界面命令下不区分大小写; 1.2. 文件格式区分 windows用扩展名区分文件;如.exe代表执行文件,.txt代表文本文件,.…

.net 调用海康SDK的跨平台解决方案

📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!📢本文作者:由webmote 原创📢作者格言:新的征程,我们面对的不仅仅是技术还有人心,人心不可测,海水不可量,唯有技术,才是深沉黑夜中的一座闪烁的灯塔序言 上2篇海康SDK使用以及常见的坑…

玩转Easysearch语法

Elasticsearch 是一个基于Apache Lucene的开源分布式搜索和分析引擎,广泛应用于全文搜索、结构化搜索、分析等多种场景。 Easysearch 作为Elasticsearch 的国产化替代方案,不仅保持了与原生Elasticsearch 的高度兼容性,还在功能、性能、稳定性…

Python基础小知识问答系列-高效遍历多个不同类型元素的迭代器

1. 问题: 当需要对多个迭代器进行相同遍历操作时,如何避免因为迭代器之间的类型或者迭代器元素 数量过大引发的问题? 2. 解决方法: 使用itertools模块中的chain函数。 示例: from itertools import chainlist_a [2,…

C语言之Const关键字与指针

目录 1 前言2 变量与指针的储存方式3 const int *var;int *const var;const int *const var;理解与区分4 总结 1 前言 实际开发过程中经常遇到const关键字作用于指针的情况,例如:const int *var;int *const var;const…

【前端】从零开始学习编写HTML

目录 一、什么是前端 二、什么是HTML 三、HTML文件的基本结构 四、HTML常见标签 4.1 注释标签 4.2 标题标签 4.3 段落标签 4.4 换行标签 4.5 格式化标签 4.6 图片标签 4.7 超链接标签 4.8 表格标签 4.9 列表标签 4.10 表单标签 (1)form标…

【vue动态组件】VUE使用component :is 实现在多个组件间来回切换

VUE使用component :is 实现在多个组件间来回切换 component :is 动态父子组件传值 相关代码实现&#xff1a; <component:is"vuecomponent"></component>import componentA from xxx; import componentB from xxx; import componentC from xxx;switch(…