【初阶数据结构篇】单链表的实现(赋源码)
文章目录
- 单链表的实现
- 代码位置
- 概念与结构
- 概念:
- 结构:
- 链表的性质
- 链表的分类
- 单链表的实现
- 单链表的创建和打印及销毁
- 单链表的创建
- 单链表的打印
- 单链表的销毁
- 单链表的插入
- 单链表头插
- 单链表尾插
- 单链表在指定位置之前插入数据
- 单链表在指定位置之后插入数据
- 单链表的删除
- 单链表的头删
- 单链表的尾删
- 单链表在指定位置删除数据
- 单链表在指定位置之后删除数据
- 单链表的查找指定位置节点
单链表的实现
代码位置
[Gitee](sllist/sllist · petrichor/2024-summer-c-language - 码云 - 开源中国 (gitee.com))
概念与结构
概念:
链表是⼀种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
类比火车地铁,都是一节一节的
- 淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉/ 加上,不会影响其他车厢,每节车厢都是独立存在的。
在链表⾥,每节“⻋厢”是什么样的呢?
结构:
与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/结点”。
结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量)。
- 图中指针变量plist保存的是第⼀个结点的地址,我们称plist此时“指向”第⼀个结点,如果我们希望 plist“指向”第⼆个结点时,只需要修改plist保存的内容为0x0012FFA0。
- 链表中每个结点都是独立申请的(即需要插⼊数据时才去申请⼀块结点的空间),我们需要通过指针 变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。
链表的性质
1、链式机构在逻辑上是连续的,在物理结构上不⼀定连续
2、结点⼀般是从堆上申请的
3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续
链表的分类
链表的结构⾮常多样,以下情况组合起来就有8种(2x2x2)链表结构:
链表说明:
- 虽然有这么多的链表的结构,但是我们实际中最常⽤还是两种结构:单链表和双向带头循环链表
- 无头单向非循环链表:结构简单,⼀般不会单独⽤来存数据。实际种更多是作为其他数据结构的⼦结构,如哈希桶、图的邻接表等等。另外这种结构在笔试⾯试中出现很多。
- 带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都是带头双向循环链表。但是这个结构虽然结构复杂,但是使⽤代码实现以后会发现结构会带来很多优势,实现反⽽简单了,在下一篇博客我们将进行双向链表的实现。
单链表的实现
即不带头单向不循环链表
单链表的创建和打印及销毁
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. 在尾插/尾删中,都需要依据链表是否为空/链表是否多于一个节点来分情况讨论,目的是避免对空指针进行解引用造成的错误。
以上就是单链表的实现方法啦,各位大佬有什么问题欢迎在评论区指正,您的支持是我创作的最大动力!❤️
相关文章:

【初阶数据结构篇】单链表的实现(赋源码)
文章目录 单链表的实现代码位置概念与结构概念:结构: 链表的性质链表的分类单链表的实现单链表的创建和打印及销毁单链表的创建单链表的打印单链表的销毁 单链表的插入单链表头插单链表尾插单链表在指定位置之前插入数据单链表在指定位置之后插入数据 单…...

LeetCode 2844.生成特殊数字的最少操作(哈希表 + 贪心)
给你一个下标从 0 开始的字符串 num ,表示一个非负整数。 在一次操作中,您可以选择 num 的任意一位数字并将其删除。请注意,如果你删除 num 中的所有数字,则 num 变为 0。 返回最少需要多少次操作可以使 num 变成特殊数字。 如…...
昇思MindSpore 应用学习-基于 MindSpore 实现 BERT 对话情绪识别
基于 MindSpore 实现 BERT 对话情绪识别 模型简介 BERT全称是来自变换器的双向编码器表征量(Bidirectional Encoder Representations from Transformers),它是Google于2018年末开发并发布的一种新型语言模型。与BERT模型相似的预训练语言模…...

【初阶数据结构篇】顺序表和链表算法题
文章目录 顺序表算法题移除元素删除有序数组中的重复项合并两个有序数组 链表算法题移除链表元素反转链表链表的中间结点合并两个有序链表链表分割链表的回文结构 顺序表算法题 不熟悉顺序表的可以先了解一下 顺序表实现方法 移除元素 给你一个数组 nums 和一个值 val&#x…...
使用weex进行APP混合开发
Weex 是一个用于构建高性能原生应用的框架,它使用 Vue.js 的语法和组件模型,允许开发者使用 HTML、CSS 和 JavaScript 来编写应用,同时能够编译成原生应用。Weex 主要由阿里巴巴集团开发,并且已经被多个公司采用。 下面是使用 We…...
C++stl大根堆/小根堆的创建与记忆
priority_queue<int, vector<int>, greater<int>> heap; 这行代码在 C 中声明了一个优先队列 heap,其元素类型为 int,使用 vector<int> 作为其底层容器,并且指定了 greater<int> 作为比较函数对象。 这里的关…...

visual studio性能探测器使用案列
visual studio性能探测器使用案列 在visual studio中,我们可以使用自带的工具对项目进行性能探测,具体如下 1.选择性能探查器 Vs2022/Vs2019中打开方式: Vs2017打开方式: 注意最好将解决方案配置为:Release Debu…...

redis的代码开发
redis是什么? 前提:官网地址https://redis.io 1.Redis是一个开源的,key,value格式的,内存型数据结构存储系统;它可用作数据库、缓存和消息中间件。 value支持多种类型的数据结构如strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglo…...

嗷呜,就问你接不接?
...

避免过拟合,参数大模型强,正则让模型不要走偏
1、加入惩罚项L1【绝对值】 和L2【默认 平方】,降低噪音的影响,减少权重W的值 2、丢弃法 层与层之间加入噪音,只能在全连接层使用 无偏差加入噪音 p为丢弃的概率 x 当概率p是0 否则为除以(1-p) 丢弃概率p 一般为0.1 0.5 def drop_out(x…...

vue+element-ui的列表查询条件/筛选条件太多以下拉选择方式动态添加条件(支持全选、反选、清空)
1、此功能已集成到TQueryCondition组件中 2、最终效果 3、具体源码(新增moreChoose.vue) <template><el-popoverpopper-class"t_query_condition_more":bind"popoverAttrsBind"ref"popover"v-if"allcheckList.length>0"…...

LLM的训练与推断
LLM的训练与推断 目前比较流行的大模型一般都是自回归模型。在推理时,它类似于RNN,每次计算下一个token的概率。也就是说,如果除去最开始的输入情况下,最终推理长度为n的话,就需要计算n次。但是训练却是并行化的。 在…...
uniapp使用WebSocket uniapp使用WebSocket Uniapp整合WebSocket uniapp使用 websocket
uniapp使用WebSocket uniapp使用WebSocket Uniapp整合WebSocket uniapp使用 websocket 前言1、Socket.js2、main.js引入3、组件中调用 前言 代码中的示例只在 H5、APP环境下成功运行,小程序环境下如果无效,需要使用预编译 - 条件性的编译,适…...
SSH Exporter:基于Prometheus的远程系统性能监控神器
SSH Exporter English | 中文 介绍 SSH Exporter 是一个基于 Prometheus 规范的监控工具,通过 SSH 协议远程收集目标服务器的系统性能数据,如 CPU 使用率、内存使用情况、磁盘和网络 I/O 等,并将这些数据暴露为 Prometheus 格式的 metrics…...
Docker基础概念
Docker 是一个流行的容器化平台,它使开发者能够打包他们的应用程序及其依赖项到一个轻量级、可移植的容器中。这有助于确保应用程序无论在哪里运行都能获得一致的结果。以下是 Docker 的几个基础概念的详细解释: 1. Docker 镜像 (Image) 定义: Docker …...
小白进阶为大神
编程已成为当代大学生的必备技能,但面对众多编程语言和学习资源,新生们常常感到迷茫。如何选择适合自己的编程语言?如何制定有效的学习计划?如何避免常见的学习陷阱?今天,我就来分享一下这方面的经验和知识…...

2024最新Python和PyCharm的安装教程
Python和PyCharm的安装教程如下: Python安装教程 一、下载Python安装包 访问Python官方网站:Welcome to Python.org。 点击页面上方的“Downloads”链接。 在下载页面,选择“Windows”系统(以Windows系统为例)&…...
数据库死锁:深入解析与应对策略
在数据库管理系统中,死锁是一个常见且棘手的问题,它可能导致系统性能下降、事务延迟甚至完全阻塞。本文将深入探讨数据库死锁的概念、产生原因、检测方法以及预防与解决策略,帮助读者更好地理解和应对这一挑战。 一、什么是数据库死锁&#…...

Python入门宝藏《看漫画学Python》,495页漫画带你弄清python知识点!简单易懂 | 附PDF全彩版
华为出品的《看漫画学Python》全彩PDF教程是一本适合Python初学者的学习资料,通过漫画的形式将复杂的Python技术问题简单化,使学习过程更加生动有趣。以下是对该教程的内容简介、本书概要及本书目录的详细解析: 内容简介 《看漫画学Python》…...

Webshell管理工具:AntSword(中国蚁剑)
中国蚁剑是一款开源的跨平台网站管理工具,它主要面向于合法授权的渗透测试安全人员以及进行常规操作的网站管理员。 通俗的讲:中国蚁剑是 一 款比菜刀还牛的shell控制端软件。 一、中国蚁剑下载 1. 下载 AntSword-Loader https://github.com/AntSwordP…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...

回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...