数据结构之单链表

前言:上一篇文章我们了解到顺序表,这一次来看另一种线性表-------单链表。

1. 单链表的概念

单链表,想必很多人会感到陌生吧。那么,到底什么是单链表呢?先了解清楚单链表的概念及特性,才能够更好的实现单链表。

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

单链表就像火车车厢一样,每节车厢都通过链条进行链接。

在这里插入图片描述

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

在这里插入图片描述

不过,在数据结构里“车厢”有一个专属名词-----节点(结点)。与顺序表不同的是,链表里的每节车厢都是独立申请下来的空间,我们称之为节点。

节点主要由两部分组成,当前节点要保存的数据和保存下一个节点的地址

链表的性质

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

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

3.从堆上申请出来的空间可能连续也可能不连续

现在我们已经了解到了链表的结构,接下来要该如何设计链表呢?在节点里需要保存数据及保存下一个节点的地址。这是两个不同的数据类型,因此使用结构体,也便于代码的可读性与可维护性

2. 单链表的实现

2.1 单链表的定义

//链表存储数据类型
typedef int SLDataType;
//单链表的定义
typedef struct SinglyLinkedList
{SLDataType data;//保存的数据struct SinglyLinkedList* next;//存储下一个节点的地址
}SList;

2.2 单链表的接口

//打印单链表
void DisplaySList(SList* phead);//尾插
void SListPushBack(SList** pphead, SLDataType x);//尾删
void SListPopBack(SList** pphead);//头插
void SListPushFront(SList** pphead, SLDataType x);//头删
void SListPopFront(SList** pphead);//单链表查找
SList* SListFind(SList* phead, SLDataType x);//从指定位置之后开始插入数据
void SListInsertAfter(SList** pphead, SList* pos, SLDataType x);//从指定位置之前插入数据
void SListInsert(SList** pphead, SList* pos, SLDataType x);//从指定位置删除数据
void SListErase(SList** pphead, SList* pos);//销毁链表
void SListDestroy(SList** pphead);

2.2.1 单链表尾插

//尾插
void SListPushBack(SList** pphead, SLDataType x)
{assert(pphead);//开辟一个节点大小的空间,开辟失败就退出程序。/*SList* newnode = (SList*)malloc(sizeof(SList));if (NULL == newnode){printf("malloc fail\n");exit(-1);}newnode->data = x;newnode->next = NULL;*///SListBuyNewNode是一个创建新节点的函数,不仅仅在尾插//中需要创建节点,在头插中也需要。因此为了避免代码冗余以//及代码的可读性与可维护性,将创建新节点的功能封装成一个//函数来实现。SList* newnode = SListBuyNewNode(pphead, x);//单链表为空的情况if (NULL == *pphead){//plist为空,让plist指向newnode,指向链表的头结点*pphead = newnode;}else{SList* tail = *pphead;//找尾节点while (NULL != tail->next){tail = tail->next;}//连接新的节点tail->next = newnode;}}

在这里插入图片描述

尾插数据需要一个节点大小的空间,用来存储数据及保存下一个节点的地址。因此首先申请一块节点大小的空间

接下来又分两种情况:

1.plist指向空,需要让plist指向链表头结点

2.plist不指向空,新申请的结点直接连接在链表尾节点的后面

在这里插入图片描述

assert(pphead);
pphead保存的是一级指针变量plist的地址,所以pphead一定不为
空。对pphead进行断言,是为了防止参数传输错误。

还有一个问题,不知道大家发现没有。这里为什么要用二级指针呢?答案很简单。plist是一个结构体指针,在尾插中有可能要改变plist,因此要用二级指针。这涉及到了实参与形参的关系。

2.2.2 创建新节点

//创建新节点
SList* SListBuyNewNode(SList** pphead,SLDataType x)
{SList* newnode = (SList*)malloc(sizeof(SList));if (NULL == newnode){printf("malloc fail\n");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}

2.2.3 打印单链表

//打印单链表
//这里也可以用二级指针来接收,保持代码风格的一致性
void DisplaySList(SList* phead)
{SList* cur = phead;while (NULL != cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}
SList* cur=phead,此时cur指向了链表的头结点,只要cur不等于
NULL,就打印链表节点里的数据。然后更新cur,指向下一个节点。

2.2.4 单链表尾删

//尾删
void SListPopBack(SList** pphead)
{assert(pphead);//单链表为空的情况assert(*pphead);//单链表至少有一个节点if (NULL == (*pphead)->next){free(*pphead);*pphead = NULL;}else{SList* tail = *pphead;SList* prev = NULL;//找尾节点while (NULL != tail->next){prev = tail;tail = tail->next;}prev->next = NULL;//释放尾节点的空间free(tail);tail = NULL;}
}

这里包含了3种情况:

1.plist指向空,代表没有链表(没有数据可以删除)

2.有多个节点

3.只有一个节点

画图分析:

在这里插入图片描述


在这里插入图片描述

1.尾删节点首先将尾节点的空间还给操作系统,然后再让尾节点的前
一个节点指向空(防止野指针的出现)
2.一直尾删,直到剩下一个节点。这个时候再尾删就要改变plist指针
了,因此尾删也需要二级指针
3.在尾删节点的时候,需要找到尾节点才能删除,由于单链表具有单
向性,因此还需要一个前驱指针,用来记录尾节点的前一个节点。

2.2.5 单链表头插

//头插
void SListPushFront(SList** pphead, SLDataType x)
{assert(pphead);//创建新节点SList* newnode = SListBuyNewNode(pphead, x);//新节点成为链表的头结点newnode->next = *pphead;//plist指向链表的头结点*pphead = newnode;
}

在这里插入图片描述

注意:一定要先让新节点的next保存当前plist的值,再更新plist,指向新的头结点

2.2.6 单链表头删

//头删
void SListPopFront(SList** pphead)
{assert(pphead);//单链表为空的情况assert(*pphead);//记录当前头结点SList* cur = *pphead;//更新plist*pphead = (*pphead)->next;//释放头结点的空间free(cur);cur = NULL;
}

在这里插入图片描述

注意:
1.先让plist指向当前头结点的下一个节点,如果先删除当前头
结点,就找不到下一个节点了。
2.释放当前头结点的空间。
2.plist为空,说明链表为空,没有数据删除。

2.2.7 单链表查找

//单链表查找
SList* SListFind(SList* phead, SLDataType x)
{//单链表为空不查找assert(phead);SList* cur = phead;while (NULL != cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}

从头结点开始遍历进行查找,找到了就返回该节点的地址,否则返回NULL

2.2.8 从指定位置之后开始插入数据

//从指定位置之后开始插入数据
void SListInsertAfter(SList** pphead, SList* pos, SLDataType x)
{assert(pphead);//创建新节点SList* newnode = SListBuyNewNode(pphead, x);//链表为空if (NULL == *pphead){*pphead = newnode;}else{SList* cur = *pphead;//找pos位置while (NULL != cur){if (cur == pos){newnode->next = cur->next;cur->next = newnode;}cur = cur->next;}}
}

在这里插入图片描述

从头结点开始遍历找pos位置,找到pos位置将新节点插入进去。

注意:让新节点的next保存pos位置下一个节点的地址,再让pos位置的节点的next保存新节点的地址

2.2.9 从指定位置之前插入数据

//从指定位置之前插入数据
void SListInsert(SList** pphead, SList* pos, SLDataType x)
{assert(pphead);//创建新节点SList* newnode = SListBuyNewNode(pphead, x);if (NULL == *pphead){*pphead = newnode;}else{SList* cur = *pphead;//记录pos位置前一个节点SList* prev = NULL;while (NULL != cur){if (cur == pos){newnode->next = prev->next;prev->next = newnode;}prev = cur;cur = cur->next;}}
}

在这里插入图片描述

从头结点开始遍历找pos位置,找到pos位置,让pos位置的前一个节点的next保存新节点的地址,新节点的next保存pos位置节点的地址

2.2.10 从指定位置删除数据

//从指定位置删除数据
void SListErase(SList** pphead, SList* pos)
{assert(pphead);//单链表为空assert(*pphead);SList* cur = *pphead;//找pos位置while (NULL != cur->next){if (cur->next == pos){cur->next = pos->next;free(pos);pos = NULL;}cur = cur->next;}
}

在这里插入图片描述

单链表不能为空,为空说明没有数据可以删除。找到pos位置,让pos位置的前一个节点的next指向pos位置节点的下一个节点,然后再释放掉pos位置的节点

2.2.11 销毁链表

//销毁链表
void SListDestroy(SList** pphead)
{assert(pphead);SList* cur = *pphead;while (NULL != cur){SList* prev = cur;cur = cur->next;free(prev);prev = NULL;}*pphead=NULL;
}
从头结点开始遍历,先让cur指向头结点的下一个节点,再释放掉当前
的头结点。最后再让*pphead=NULL。在循环过程中,*pphead的指
向一直没有发生改变,销毁链表时,也要让plist指向空,防止出现野
指针。

3. 单链表完整代码的实现

SList.h的实现

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>typedef int SLDataType;typedef struct SinglyLinkedList
{SLDataType data;struct SinglyLinkedList* next;//存储下一个节点的地址
}SList;//打印单链表
void DisplaySList(SList* phead);//尾插
void SListPushBack(SList** pphead, SLDataType x);//尾删
void SListPopBack(SList** pphead);//头插
void SListPushFront(SList** pphead, SLDataType x);//头删
void SListPopFront(SList** pphead);//单链表查找
SList* SListFind(SList* phead, SLDataType x);//从指定位置之后开始插入数据
void SListInsertAfter(SList** pphead, SList* pos, SLDataType x);//从指定位置之前插入数据
void SListInsert(SList** pphead, SList* pos, SLDataType x);//从指定位置删除数据
void SListErase(SList** pphead, SList* pos);//销毁链表
void SListDestroy(SList** pphead);

SList.c实现

#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"//打印单链表
void DisplaySList(SList* phead)
{SList* cur = phead;while (NULL != cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}//创建新节点
SList* SListBuyNewNode(SList** pphead,SLDataType x)
{SList* newnode = (SList*)malloc(sizeof(SList));if (NULL == newnode){printf("malloc fail\n");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}//尾插
void SListPushBack(SList** pphead, SLDataType x)
{assert(pphead);/*SList* newnode = (SList*)malloc(sizeof(SList));if (NULL == newnode){printf("malloc fail\n");exit(-1);}newnode->data = x;newnode->next = NULL;*/SList* newnode = SListBuyNewNode(pphead, x);//单链表为空的情况if (NULL == *pphead){*pphead = newnode;}else{SList* tail = *pphead;while (NULL != tail->next){tail = tail->next;}tail->next = newnode;}}//尾删
void SListPopBack(SList** pphead)
{assert(pphead);//单链表为空的情况assert(*pphead);//单链表至少有一个节点if (NULL == (*pphead)->next){free(*pphead);*pphead = NULL;}else{SList* tail = *pphead;SList* prev = NULL;while (NULL != tail->next){prev = tail;tail = tail->next;}prev->next = NULL;free(tail);tail = NULL;}
}//头插
void SListPushFront(SList** pphead, SLDataType x)
{assert(pphead);/*SList* newnode = (SList*)malloc(sizeof(SList));if (NULL == newnode){printf("malloc fail\n");exit(-1);}newnode->data = x;newnode->next = NULL;*/SList* newnode = SListBuyNewNode(pphead, x);//至少有一个节点/*if (NULL == *pphead){*pphead = newnode;}else{newnode->next = *pphead;*pphead = newnode;}*/newnode->next = *pphead;*pphead = newnode;
}//头删
void SListPopFront(SList** pphead)
{assert(pphead);//单链表为空的情况assert(*pphead);SList* cur = *pphead;*pphead = (*pphead)->next;free(cur);cur = NULL;
}//单链表查找
SList* SListFind(SList* phead, SLDataType x)
{//单链表为空不查找assert(phead);SList* cur = phead;while (NULL != cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}//从指定位置之后开始插入数据
void SListInsertAfter(SList** pphead, SList* pos, SLDataType x)
{assert(pphead);SList* newnode = SListBuyNewNode(pphead, x);if (NULL == *pphead){*pphead = newnode;}else{SList* cur = *pphead;while (NULL != cur){if (cur == pos){newnode->next = cur->next;cur->next = newnode;}cur = cur->next;}}
}//从指定位置之前插入数据
void SListInsert(SList** pphead, SList* pos, SLDataType x)
{assert(pphead);SList* newnode = SListBuyNewNode(pphead, x);if (NULL == *pphead){*pphead = newnode;}else{SList* cur = *pphead;SList* prev = NULL;while (NULL != cur){if (cur == pos){newnode->next = prev->next;prev->next = newnode;}prev = cur;cur = cur->next;}}
}//从指定位置删除数据
void SListErase(SList** pphead, SList* pos)
{assert(pphead);//单链表为空assert(*pphead);SList* cur = *pphead;while (NULL != cur->next){if (cur->next == pos){cur->next = pos->next;free(pos);pos = NULL;}cur = cur->next;}
}//销毁链表
void SListDestroy(SList** pphead)
{assert(pphead);SList* cur = *pphead;while (NULL != cur){SList* prev = cur;cur = cur->next;free(prev);prev = NULL;}*pphead=NULL;
}

test.c的实现

#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"void TestSList1()
{SList* plist = NULL;SListPushBack(&plist, 1);SListPushBack(&plist, 2);SListPushBack(&plist, 3);SListPushBack(&plist, 4);SListPushBack(&plist, 5);DisplaySList(plist);SListPopBack(&plist);SListPopBack(&plist);SListPopBack(&plist);SListPopBack(&plist);SListPopBack(&plist);DisplaySList(plist);}void TestSList2()
{SList* plist = NULL;SListPushFront(&plist, 10);SListPushFront(&plist, 20);SListPushFront(&plist, 30);SListPushFront(&plist, 40);DisplaySList(plist);SListPopFront(&plist);DisplaySList(plist);SList* pos = SListFind(plist, 50);if (NULL != pos){printf("%d\n", pos->data);}else{printf("找不到\n");}}void TestSList3()
{SList* plist = NULL;SListInsertAfter(&plist, NULL, 10);DisplaySList(plist);SListPushBack(&plist, 1);SListPushBack(&plist, 2);SListPushBack(&plist, 3);SListPushBack(&plist, 4);SListPushBack(&plist, 5);SList* pos = SListFind(plist, 4);SListInsertAfter(&plist, pos, 20);DisplaySList(plist);pos = SListFind(plist, 2);SListInsert(&plist, pos, 30);DisplaySList(plist);pos = SListFind(plist, 3);SListErase(&plist, pos);DisplaySList(plist);SListDestroy(&plist);}int main()
{//TestSList1();//TestSList2();TestSList3();return 0;
}

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

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

相关文章

RabbitMQ死信队列

RabbitMQ死信队列 1、RabbitMQ死信队列2、代码示例2.1、队列过期2.1.1、配置类RabbitConfig&#xff08;关键代码&#xff09;2.1.2、业务类MessageService2.1.3、配置文件application.yml2.1.4、启动类2.1.5、配置文件2.1.6、测试 2.2、消息过期2.2.1、配置类RabbitConfig2.2.…

高亚科技签约酸动力,助力研发管理数字化升级

近日&#xff0c;中国企业管理软件资深服务商高亚科技与广东酸动力生物科技有限公司&#xff08;以下简称“酸动力”&#xff09;正式签署合作协议。借助高亚科技的8Manage PM项目管理软件&#xff0c;酸动力将进一步优化项目过程跟踪与节点监控&#xff0c;提升研发成果的高效…

Linux操作系统:学习进程_对进程的深入了解

目录 前言 开篇 一、进程概念 二、进程的描述与管理 1、如何描述与管理 2、Linux中的PCB-task_struct 3、对进程组织的理解 三、进程的属性 1、系统创建进程 2、查看进程 3、进程的标识符 4、退出进程 1>ctrlc 2>kill命令杀死进程 5、用户进程的创建方式…

大客户营销数字销售实战讲师培训讲师唐兴通专家人工智能大模型销售客户开发AI大数据挑战式销售顾问式销售专业销售向高层销售业绩增长创新

唐兴通 销售增长策略专家、数字销售实战导师 专注帮助企业构建面向AI数字时代新销售体系&#xff0c;擅长运用数字化工具重塑销售流程&#xff0c;提升销售业绩。作为《挑战式销售》译者&#xff0c;将全球顶尖销售理论大师马修狄克逊等理论导入中国销售业界。 核心专长&…

【Attention】ICAFusion:用于多光谱物体检测的迭代交叉注意引导的特征融合

ICAFusion: Iterative cross-attention guided feature fusion for multispectral object detection 摘要&#xff1a; 多光谱图像的有效特征融合在多光谱物体检测中起着至关重要的作用。以往的研究已经证明了使用卷积神经网络进行特征融合的有效性&#xff0c;但由于局部范围…

CSP/信奥赛C++刷题训练:经典广搜例题(2):洛谷P1135 :奇怪的电梯

CSP/信奥赛C刷题训练&#xff1a;经典广搜例题&#xff08;2&#xff09;&#xff1a;洛谷P1135 &#xff1a;奇怪的电梯 题目背景 感谢 yummy 提供的一些数据。 题目描述 呵呵&#xff0c;有一天我做了一个梦&#xff0c;梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电…

K8S群集调度二

一、污点(Taint) 和 容忍(Tolerations) 1.1、污点(Taint) 设置在node上是对pod的一种作用 节点的亲和性&#xff0c;是Pod的一种属性&#xff08;偏好或硬性要求&#xff09;&#xff0c;它使Pod被吸引到一类特定的节点 而Taint 则相反&#xff0c;它使节点能够排斥一类特…

成都郝蓉宜恺文化传媒:引领大数据应用新篇章

在信息化浪潮汹涌的今天&#xff0c;大数据被誉为新时代的“石油”&#xff0c;正在以前所未有的速度改变着我们的生活和工作方式。成都郝蓉宜恺文化传媒&#xff0c;作为大数据领域的领军企业&#xff0c;始终站在创新的前沿&#xff0c;引领着大数据应用的新篇章。 作为大数…

51c自动驾驶~合集5

我自己的原文哦~ https://blog.51cto.com/whaosoft/11563178 #MapDistill 速度精度双起飞&#xff0c;让End2End更丝滑 在线高精&#xff08;HD&#xff09;地图构建是自动驾驶领域的一项重要且具有挑战性的任务。最近&#xff0c;人们对不依赖于激光雷达等其他传感器的基于…

如何在 SAP 中直接运行原生 SQL 语句

作为 ABAP 开发应该知道&#xff0c;SAP 支持在程序中运行 ABAP SQL&#xff0c;但是如果想要运行原生 SQL&#xff0c;就要借助 SQL 编辑器了。 Ps&#xff1a;你得向 Basis 申请权限。 SQL 编辑器允许您直接执行 SQL 语句。 1 SQL 编辑器启动方式 它可以在以下 T-code 中执…

华普微隔离芯片,赋能中国新基建之光伏创新

一、华普微隔离芯片助力光伏产业发展&#xff1a;现状、应用与未来展望 当前&#xff0c;光伏行业正深陷在无序扩张、产能过剩及激烈内卷的困境之中。为打破这种恶性竞争局面&#xff0c;光伏行业未来发展的“主旋律”已定调在淘汰落后产能、倡导企业兼并重组与加速技术革新步…

时隔7年,我终于考了CISSP

七年前&#xff0c;我开启了信息安全之旅&#xff0c;将 OSG 第 4 版作为敲门砖。耗费两个月时间硬着头皮读完&#xff0c;却如坠云雾&#xff0c;全然不知其深意&#xff0c;仅仅在脑海中隐约勾勒出一个大致的知识框架。 随后&#xff0c;我幸运地找到了相关工作&#xff0c;…

中科蓝汛GPIO操作说明

概述 本篇文章介绍如何使用中科蓝汛AB5681&#xff0c;GPIO管脚使用说明。 一、第一种写法 1&#xff09;、GPIO配置输入模式 //内部上拉 GPIOBDE | BIT(4); //数字IO使能: 0为模拟IO, 1 为数字IO GPIOBDIR | BIT(4); //控制IO的方向: 0为输出, 1为输入. GPIOBFEN &…

RHCE 配置文件

配置文件 配置文件排错 1.1 配置基于主机名的 Web 服务器1.2 配置基于端口的 Web 服务器1.3 配置基于IP地址的 Web 服务器1.4 配置账号验证访问1.5 配置 https 加密服务1.6 课后习题 配置文件 配置文件vim里面内容时&#xff0c;用空格分割 #寻找配置文件 [rootlocalhost ~]# r…

笔记整理—linux驱动开发部分(8)framebuffer类设备

framebuffer显示设备。 在应用层直接抽象位向DDR中存放图片。 在操作系统中&#xff0c;将上图分为两个部分&#xff1a;驱动应用。 使用复制的方法效率十分的低&#xff0c;所以有了内存映射方法实现图片的显示。 framebuffer帧&#xff08;铺满一个屏幕&#xff09;&#xff…

智慧测绘数字化管理平台建设方案

随着信息技术的飞速发展&#xff0c;测绘地理信息与遥感专业正经历着一场革命性的变革。智慧测绘数字化管理平台的建设&#xff0c;不仅能够提高测绘数据的准确性和实时性&#xff0c;还能为城市规划、环境保护、灾害预防等领域提供强有力的数据支持。本文将探讨智慧测绘数字化…

conda的作用

conda是一个开源的包和环境管理系统&#xff0c;用于安装、管理和切换不同版本的软件包及其依赖项。它不仅支持Python&#xff0c;还适用于R、Ruby等多种编程语言。以下是详细介绍&#xff1a; 多语言支持&#xff1a;conda支持多种编程语言&#xff0c;包括但不限于Python、R、…

测试平台常见前端问题-建议收藏备忘

接下来在使用Element UI开发测试平台前端的过程中&#xff0c;难免会碰到各式各样的问题&#xff0c;因此今天我们主要整理了以下几个常见的问题和解决方案&#xff0c;方便各位能轻松玩转测试平台前端&#xff1a; Element UI更换主题颜色 拉取github资源报错问题解决 nvm管…

NC313 两个数组的交集

NC313 两个数组的交集 添加链接描述 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可** * param nums1 int整型ArrayList * param nums2 int整型ArrayList * return int整型A…

[C++刷题] 基础小知识点(4) abs() exp() 和 输入验证

分析题目, 大多数都是常规操作, 较为特殊的有: 程序需有一定的容错性, 当用户输入非法字符时, 提示用户重新输入。绝对值的实现e^x的实现 首先是 第一点 这里通过cin.fail()流判断是否合法 cin.fail()来判断当前的输入的类型和预期的是否相同&#xff0c;如不同cin.fail()返回…