【初阶数据结构篇】单链表的实现(赋源码)

文章目录

  • 单链表的实现
    • 代码位置
    • 概念与结构
      • 概念:
      • 结构:
    • 链表的性质
    • 链表的分类
    • 单链表的实现
      • 单链表的创建和打印及销毁
        • 单链表的创建
        • 单链表的打印
        • 单链表的销毁
      • 单链表的插入
        • 单链表头插
        • 单链表尾插
        • 单链表在指定位置之前插入数据
        • 单链表在指定位置之后插入数据
      • 单链表的删除
        • 单链表的头删
        • 单链表的尾删
        • 单链表在指定位置删除数据
        • 单链表在指定位置之后删除数据
      • 单链表的查找指定位置节点

单链表的实现

代码位置

[Gitee](sllist/sllist · petrichor/2024-summer-c-language - 码云 - 开源中国 (gitee.com))

概念与结构

概念:

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

​ 类比火车地铁,都是一节一节的

在这里插入图片描述

  • 淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉/ 加上,不会影响其他车厢,每节车厢都是独立存在的。

在链表⾥,每节“⻋厢”是什么样的呢?
在这里插入图片描述

结构:

与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/结点”。

结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量)。

  • 图中指针变量plist保存的是第⼀个结点的地址,我们称plist此时“指向”第⼀个结点,如果我们希望 plist“指向”第⼆个结点时,只需要修改plist保存的内容为0x0012FFA0。
  • 链表中每个结点都是独立申请的(即需要插⼊数据时才去申请⼀块结点的空间),我们需要通过指针 变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。

链表的性质

1、链式机构在逻辑上是连续的,在物理结构上不⼀定连续

2、结点⼀般是从堆上申请的

3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续


链表的分类

链表的结构⾮常多样,以下情况组合起来就有8种(2x2x2)链表结构

在这里插入图片描述

链表说明:

在这里插入图片描述

  • 虽然有这么多的链表的结构,但是我们实际中最常⽤还是两种结构:单链表和双向带头循环链表
    1. 无头单向非循环链表:结构简单,⼀般不会单独⽤来存数据。实际种更多是作为其他数据结构的⼦结构,如哈希桶、图的邻接表等等。另外这种结构在笔试⾯试中出现很多。
    2. 带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都是带头双向循环链表。但是这个结构虽然结构复杂,但是使⽤代码实现以后会发现结构会带来很多优势,实现反⽽简单了,在下一篇博客我们将进行双向链表的实现。

单链表的实现

即不带头单向不循环链表

单链表的创建和打印及销毁


SList.h(其中方法会一一讲到)

  • 定义链表结构
  • 将存储数据类型重命名(方便之后替换->例如我们要求单链表内存储char类型数据,只用改一行代码即可)
  • 函数的声明,声明的时候参数只需要类型就可以了,名字加不加都一样
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode 
{SLTDataType data;struct SListNode* next;
}SLTNode;void creatlist();
SLTNode* SLTBuyNode(SLTDataType x);
void printlist(SLTNode*);//插入
void SLTPushBack(SLTNode**, SLTDataType);
void SLTPushFront(SLTNode**, SLTDataType);//删除
void SLTPopBack(SLTNode**);
void SLTPopFront(SLTNode**);//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);//指定位置插入数据
void SLTInsert(SLTNode**, SLTDataType, SLTNode*);
void SLTInsertAfter( SLTDataType, SLTNode*);//删除指定节点
void SLTErase(SLTNode**, SLTNode*);
void SLTEraseAfter(SLTNode*);//销毁链表
void SListDestroy(SLTNode**);

test.c

  • 用来测试我们写的函数(函数的调用)
  • 这一部分就是自己写的时候用的测试用例,随便什么都行

养成好习惯,写一个方法测试一次,不然找错误的时候会很痛苦😜

#include "sllist.h"
//实际操作中不会这么去创建链表
void creatlist()
{SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));node1->data = 1;SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));node2->data = 2;SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));node3->data = 3;SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));node4->data = 4;node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;SLTNode* plist = node1;printlist(plist);}void test1(){SLTNode* plist = NULL;//SLTPushBack(&plist, 1);//SLTPushBack(&plist, 2);//SLTPushBack(&plist, 3);//SLTPushBack(&plist, 4);SLTPushBack(NULL, 4);SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 4);SLTPushFront(&plist, 3);//SLTPopBack(&plist);//SLTPopBack(&plist);//SLTPopFront(&plist);//SLTPopFront(&plist);//SLTNode* m=SLTFind(plist, 3);//if (m!= NULL)//{//	printf("找到了\n");//}SLTInsert(&plist, 3, m);//SLTInsertAfter( 6, m);//	SLTErase(&plist, m);//SLTEraseAfter(m);SListDestroy(&plist);printlist(plist);}int main()
{//creatlist();test1();return 0;
}
单链表的创建

可以看到在creatlist中我们是先随便申请几个节点然后将他们首尾相连

但链表的性质是每个节点都是独立申请的,即我们有需要才去申请一块节点的空间,所以我们实际中我们不会这样创建链表,只需一开始创建一个链表结点类型的指针plist,并将其置为空,表示此时链表为空,之后需要的时候我们通过插入一个节点并始终保持plist指向单链表第一个节点即可。

单链表的打印
void printlist(SLTNode* phead)
{assert(phead);SLTNode* p1 = phead;while (p1){printf("%d ", p1->data);p1 = p1->next;}
}
  • 一般情况下,单链表已知的都是指向第一个节点的指针plist,因为我们只是打印不需要改变plist的指向,即不需要改变plist的值,所以我们用一级指针即可。
  • 基本思想仍是遍历

在这里插入图片描述

单链表的销毁
void SListDestroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* p1 = pcur;pcur = pcur->next;free(p1);p1 = NULL;}*pphead = NULL;
}

- 这里我们要改变plist的值了,所以传二级指针(其实也可以传一级,只不过在调用完后别忘了把plist置为空哦(❁´◡`❁))


单链表的插入

单链表头插

既然要插入我们就要先申请一块空间

SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));if (node == NULL){perror("malloc fail!");exit(1);}node->data = x;node->next = NULL;return node;
}
  • 将申请新节点的函数分装起来,便于调用
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}
  • 连接新节点和plist
  • 再让plist指向新节点
单链表尾插
void SLTPushBack(SLTNode** pphead , SLTDataType x)
{//申请新节点assert(pphead);SLTNode* newnode = SLTBuyNode(x);if (*pphead==NULL){*pphead = newnode;}else{SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}
}
  • 分两种情况
    • 链表为空,plist直接指向新节点
    • 链表不为空,找尾节点再插入
单链表在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTDataType x, SLTNode* pos)
{assert(pphead && pos);if (pos == *pphead){SLTPushFront(pphead,x);}else{SLTNode* newnode = SLTBuyNode(x);SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}newnode->next = pos;prev->next = newnode;}
}
  • 分两种情况
    • 若是pos和plist相同,说明就是头插,调用即可
    • 第二种情况,找到pos之前一个节点,一定要先改newnode的next指针,否则找不到pos下一个节点了
单链表在指定位置之后插入数据
void SLTInsertAfter( SLTDataType x, SLTNode* pos)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}
  • 还是记得先改插入进来newnode的next指针

单链表的删除

单链表的头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}
  • 删除都别忘了判断链表是否为空,即plist是否为空
单链表的尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}if (prev)//处理只有一个节点的情况{prev->next = NULL;prev=NULL;}else*pphead = NULL;free(ptail);ptail = NULL;
}
  • 同样分两种情况

    • 链表多于一个节点时,找尾节点以及其前一个节点,将尾节点释放并把前一个节点next指针置为空

    • 当链表只有一个节点时,不需要将前一个节点next指针置为空,此时需要将plist置为空

单链表在指定位置删除数据
void SLTErase(SLTNode** pphead, SLTNode* pos )
{assert(pos && pphead&&*pphead);if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}
  • 分两种情况
    • pos和plist相同,即为头删
    • 第二种情况,先找到pos之前节点,再进行指针的更改即可
单链表在指定位置之后删除数据
void SLTEraseAfter( SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;	
}
  • 先保存pos之后节点,改变pos的next指针后再释放del

单链表的查找指定位置节点

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{assert(phead);SLTNode* pcur = phead;while (pcur){if (pcur->data==x){return pcur;}pcur = pcur->next;}return NULL;
}
  • 找到就返回指向节点的指针,否则返回空
  • 老规矩遍历就行了

SList.c(完整版)

#define _CRT_SECURE_NO_WARNINGS 1
#include "sllist.h"
void printlist(SLTNode* phead)
{SLTNode* p1 = phead;while (p1){printf("%d ", p1->data);p1 = p1->next;}
}SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));if (node == NULL){perror("malloc fail!");exit(1);}node->data = x;node->next = NULL;return node;
}void SLTPushBack(SLTNode** pphead , SLTDataType x)
{//申请新节点assert(pphead);SLTNode* newnode = SLTBuyNode(x);if (*pphead==NULL){*pphead = newnode;}else{SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}
}void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}void SLTPopBack(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}if (prev)//处理只有一个节点的情况prev->next = NULL;else*pphead = NULL;free(ptail);ptail = NULL;
}void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{assert(phead);SLTNode* pcur = phead;while (pcur){if (pcur->data==x){return pcur;}pcur = pcur->next;}return NULL;}void SLTInsert(SLTNode** pphead, SLTDataType x, SLTNode* pos)
{assert(pphead && pos);if (pos == *pphead){SLTPushFront(pphead,x);}else{SLTNode* newnode = SLTBuyNode(x);SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}newnode->next = pos;prev->next = newnode;}
}void SLTInsertAfter( SLTDataType x, SLTNode* pos)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}void SLTErase(SLTNode** pphead, SLTNode* pos )
{assert(pos && pphead&&*pphead);if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}void SLTEraseAfter( SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;	
}void SListDestroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* p1 = pcur;pcur = pcur->next;free(p1);p1 = NULL;}*pphead = NULL;
}

在单链表实现的函数中,特别有两点要注意:

1. 涉及到plist(指向第一个节点的指针)的指向改变时,一定记得传plist的地址,使用二级指针
2. 在尾插/尾删中,都需要依据链表是否为空/链表是否多于一个节点来分情况讨论,目的是避免对空指针进行解引用造成的错误。

以上就是单链表的实现方法啦,各位大佬有什么问题欢迎在评论区指正,您的支持是我创作的最大动力!❤️

请添加图片描述

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

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

相关文章

SAP MM学习笔记47 - 实地棚卸(库存盘点)

上一章讲了SAP MM模块种的出力管理&#xff08;消息管理&#xff09;。 SAP MM学习笔记46 - 购买中的出力管理(消息管理)-CSDN博客 本章讲库存盘点的相关内容。 - 库存盘点分3步走&#xff1a;实地棚卸票登录&#xff0c;检数入力&#xff0c;差异分析及转记 - SAP中还提供了…

学习笔记 韩顺平 零基础30天学会Java(2024.7.22)

P407 接口使用细节2 P407 接口课堂练习 对于最后一个的输出&#xff1a;B因为实现了A的接口&#xff0c;所以和继承一样&#xff0c;B可以访问A的变量 P409 接口VS继承 接口对单继承机制&#xff08;是指只能继承一个类&#xff09;进行了补充 也可以理解为&#xff0c;子类通过…

【IDEA】子模块导包爆红,IDEA在错误的地方(父模块)自动添加依赖

文章目录 问题背景解决方法 问题背景 IDEA中&#xff0c;我在子模块中添加了依赖&#xff0c;整个项目编译没问题&#xff0c;但导包爆红&#xff0c;IDEA非要我在父模块中添加依赖&#xff0c;这是为什么&#xff1f;这样会导致编译出问题。 解决方法 删除.idea目录&#x…

24种设计模式介绍与6大设计原则(电子版教程)

前言 您是一个初级的 coder,可以从中领会到怎么设计一段优秀的代码&#xff1b;您是一个高级程序员&#xff0c;可以从中全面了解到设计模式以及 Java 的边角技术的使用&#xff1b;您是一个顶级的系统分析师&#xff0c;可以从中获得共鸣&#xff0c;寻找到项目公共问题的解决…

python_从csv写入到xlsx(一次性写入and分批写入)

一次性写入 import pandas as pd from openpyxl import Workbook# CSV 文件路径 csv_file D:\\desktop\\output.csv# XLSX 文件路径 xlsx_file D:\\desktop\\output.output.xlsx# 读取 CSV 文件 data pd.read_csv(csv_file)# 将 DataFrame 写入 XLSX 文件 data.to_excel(xl…

安全哈希算法:SHA算法

&#x1f3af; 主题简介 SHA&#xff08;Secure Hash Algorithm&#xff09;是比MD5更安全的哈希算法。通过案例形式了解SHA算法的原理、实现方法及注意细节。无论你是Python爱好者还是JavaScript高手&#xff0c;这篇内容都将为你提供一个深入了解SHA算法的机会。 &#x1f…

Spring Boot集成screw实现数据库文档生成

1.什么是screw&#xff1f; 在企业级开发中、我们经常会有编写数据库表结构文档的时间付出&#xff0c;从业以来&#xff0c;待过几家企业&#xff0c;关于数据库表结构文档状态&#xff1a;要么没有、要么有、但都是手写、后期运维开发&#xff0c;需要手动进行维护到文档中&…

免费【2024】springboot 成都奥科厨具厂产品在线销售系统设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

【期末考试复习】学术英语阅读

文章目录 前言1. 一篇学术论文的组成部分2. this unit2.1 Unit 1 标题第一类&#xff1a;陈述句结构&#xff08;Declarative Sentence Structure&#xff09;第二类&#xff1a;名词群结构&#xff08;Nominal Group Construction&#xff09;第三类&#xff1a;复合结构&…

响应式编程框架Reactor之 Flux 和 Mono 的介绍和区别

Flux和Mono在Reactor框架中都是响应式编程模型的重要概念,它们在处理异步数据流时发挥着重要作用,两者之间也存在一些差异。 Mono的介绍 基本概念: Mono是Reactor中的一个类,它表示一个异步的单个值或零个值的结果。Mono可以看作是一个特殊的Publisher,用于产生数据流,…

独立开发需要掌握哪些技术栈

SEO 首先要掌握的如何分析流量&#xff0c; 这里推荐所有掌握关键词分析。 掌握Semrush工具使用。 作出搜索引擎喜欢的内容&#xff0c; 降低获客成本。 这一步非常非常重呀&#xff01; 笔者自己的网站&#xff0c;www.bpmnmodeler.com 通过semrush挖掘到camunda online model…

面向切面编程(AOP)

通知类型 Grep Console插件可右键选中日志高亮显示 正常情况 异常情况(around after和目标方法在一起&#xff0c;目标方法异常后&#xff0c;around after不执行) 通知顺序 execution 需要匹配两个没有任意交集的方法时&#xff0c;可以使用两个execution annotation 自定义…

面试 SQL整理 常见的SQL面试题:大厂经典60题(一)

目录 SQL基础知识整理: 数据库基础知识 为什么要使用数据库 数据保存在内存 数据保存在文件 数据保存在数据库 什么是SQL&#xff1f; 什么是MySQL? 数据库三大范式是什么 mysql有关权限的表都有哪几个 MySQL的binlog有有几种录入格式&#xff1f;分别有什么区别&…

Ubuntu下设置文件和文件夹用户组和权限

在 Ubuntu 上&#xff0c;你可以使用 chmod 和 chown 命令来设置当前文件夹下所有文件的权限和所有者。 设置权限&#xff1a; 使用 chmod 命令可以更改文件和目录的权限。例如&#xff0c;要为当前文件夹下的所有文件和子目录设置特定权限&#xff0c;可以使用以下命令&#x…

vue3<script setup>中插槽的使用

在 Vue 3 中&#xff0c;<script setup> 语法糖极大地简化了组件的编写方式&#xff0c;使得代码更加简洁。在使用 <script setup> 时&#xff0c;插槽&#xff08;Slots&#xff09;的使用方式和在常规 <script> 中类似&#xff0c;但可能需要稍微注意一些细…

搭建自己的金融数据源和量化分析平台(一):系统架构设计

0x00 前言 由于某得的数据实在是太贵&#xff0c;某花顺免费版又不提供专业的数据分析工具&#xff0c;Tushare也开始收费&#xff0c;因此决定基于python和MySQL搭建一个自己的金融数据库。期望做到仅依靠交易所、巨潮资讯网等官方的公开数据实现&#xff0c;尽量不依靠某花顺…

Python3 多线程中的线程睡眠技巧

各类学习教程下载合集 ​​https://pan.quark.cn/s/874c74e8040e​​ 在现代编程中&#xff0c;多线程是一种重要的工具&#xff0c;可以帮助我们实现并发和并行处理&#xff0c;从而提高程序的效率。在 Python 中&#xff0c;通过 ​​threading​​ 模块&#xff0c;我们可…

绿联UGOS Pro系统虚拟机部署Windows 11详细教程(跳账户登录,安装内存驱动)

绿联UGOS Pro系统虚拟机部署Windows 11详细教程&#xff08;跳账户登录&#xff0c;安装内存驱动&#xff09; 哈喽小伙伴们好&#xff0c;我是Stark-C~ 自从绿联更新UGOS Pro系统之后&#xff0c;简直犹如脱胎换骨般&#xff0c;为大家带来了前所未有的全新体验&#xff0c;…

基于单片机控制的红外热释电家庭防盗报警器硬件系统设计

【摘要】 随着社会的发展和人们安全意识的提高&#xff0c;传统的家庭防盗系统不能适应现代生活中多变的环境。本文设计开发的红外热释电家庭防盗报警器能改善传统防盗系统结构复杂&#xff0c;计算繁琐、价格昂贵、监控盲区等缺陷&#xff0c;主要由单片机模块、复位电路模块、…