当前位置: 首页 > news >正文

手撕单链表(C语言)

目录

1.单链表的物理结构

2.头文件的实现

3.SList.c文件的实现

3.1尾插、创建节点

3.2打印

3.3头插

3.4尾删

3.5头删

3.6查找

3.7指定位置之前插入数据

3.8指定位置之后插入数据

3.9删除指定位置节点

3.10删除pos之后的节点

3.11销毁链表

4 所有的代码


1.单链表的物理结构

众所周知单链表是线性表的一种,线性表是逻辑结构连续、物理结构不一定连续的结构,我们的单链表就是逻辑上连续但物理结构上不一定连续的结构。

那么它的逻辑图长什么样呢?(如下图所示)

上面的图片中我们展示的是一种无头单向不循环链表,plist指针指向的就是单链表头节点的空间的地址。我们将每一个小方格称为节点,每个节点的结构中包括数据值和指向下一个节点的地址。

上面的代码就是我们这无头单向不循环链表的结构,我们重命名int型是为了以后改数据类型的时候不要改太多次,只用在这一行把int换掉就可以了。然后我们把单链表结构体重命名也是为了我们书写方便。我们这里使用三个文件来实现无头单向不循环链表的内容。分别是test.c源文件(用来测试代码的文件)、SList.c源文件(用来实现接口的文件)、SList.h头文件(用来进行接口的声明,各种头文件的调用,定义数据结构)。最后我会把代码全部放上来,那么我们开始来体会这个过程。

2.头文件的实现

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>//定义链表节点的结构
typedef int SLDataType;
typedef struct SListNode
{SLDataType data;//要保存的数据struct SListNode* next;
}SLNode;//创建几个节点组成一个链表,并打印链表
void SLNPrint(SLNode* phead);
//尾插
void SLPushBack(SLNode** pphead, SLDataType x);
//头插
void SLPushFront(SLNode** pphead, SLDataType x);
//删除
void SLPopBack(SLNode** pphead);
void SLPopFront(SLNode** pphead);SLNode* SLFind(SLNode** pphead, SLDataType x);//指定位置的插入和删除//在指定位置之前插入数据
void SLInsert(SLNode** pphead,SLNode* pos,SLDataType x);
//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x);//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos);
//删除pos之后节点
void SLEraseAfter(SLNode* pos);
//销毁链表
void SListDesTroy(SLNode** pphead);

引用头文件我们就不多说了,单链表的结构我们上面也讲过了,接下来就来讲解一下声明这些接口的时候我们要注意的事项:由于在test.c文件中我们定义的是SLNode*指针,我们知道使用函数传参时,形参是实参的一份临时拷贝,所以为了修改我们的链表我们要传二级指针才能修改链表。

3.SList.c文件的实现

3.1尾插、创建节点

我们执行尾插前要先进行断言一下,这是为了防止传进来一个空指针,接下来就是创建节点:

创建节点我们只需要传一个x值就可以了,然后把返回值类型定位SLNode*型,在这个接口中我们先用malloc开辟出一块空间,然后再把x的值给到结构体中的data,之后我们再把next指针置为NULL,接下来就可以把节点指针返回了。

我们再回到上面,创建好节点后我们还要看一看是否传进来的是空链表,如果是的话我们把新的节点给头节点然后返回头节点,如果不是空链表的话,我们用pcur指针来遍历整个链表进行找尾操作,找到尾指针之后我们把尾指针的next指向新的节点。

3.2打印

我们的尾插操作有没有成功可以通过两种方式,一种是调试,一种是打印出来看看(粗略检查)。

我们还是让pcur指针来进行遍历,每遍历一个节点我们就打印一个节点,最后打印一个NULL,如果是空链表我们就会直接打印NULL。

这样我们就可以看出确实是尾插成功了。

3.3头插

头插的操作跟尾插差不多,我们先断言指针是否为NULL,不是的话我们创建一个新的节点,这里我们注意一下顺序,我们要先把新节点的next指向原来的头节点,再把头节点指针指向新的节点,倘若我们先进行把头节点指针指向新的节点我们就会发现我们找不到原来的头节点了,这就是我们要注意的一个地方。

好,让我们来看看效果:

3.4尾删

尾删的操作我们不需要传入x值了,只需要二级指针就行了,第一步还是要进行断言,我们这里我们还需要断言一下它的一级指针,因为我们既然要进行删除操作,我们的链表总不能是空链表吧,接下来我们处理只有一个节点的情况,只有一个节点的话,我们直接把头节点空间释放,地址指向空就可以了,如果节点不只有一个,我们就需要找尾节点和尾节点的前一个节点,我们先创建一个ptail进行遍历,ptail指向的是最后一个节点,prev则是前一个节点,我们把我们把将要成为尾节点的next指向原尾节点的next,再把原尾节点的空间释放掉,我们就完成了尾删的操作。

3.5头删

头删操作要简单的多,它不需要考虑只有一个节点的情况,跟尾删一样,我们先断言两次,然后创建一个del指针指向头节点,然后将头节点指向新的头节点,也就是原头节点的下一个节点,然后把del释放掉,我们再出于规范把这个野指针置为NULL。

3.6查找

这里我们查找的标准就定为查找是否有x这个元素,然后我们的第一步还是断言,之后创建一个pcur来遍历整个链表,如果找到了就返回这个地址,如果没有找到就返回NULL。

3.7指定位置之前插入数据

这里我们要多传一个参数pos,因为pos就是我们的指定位置的节点。好我们开始断言,断言之后我们创建新的节点,然后处理只有一个节点的情况,如果只有一个头节点我们就把新节点的next指向头节点,然后再让头节点的指针指向新节点;如果不只有一个节点,那么我们就创建一个指针prev来找到pos节点的前一个,然后把新节点的next指向pos,再把原来的前一个节点的next指向新的节点。

3.8指定位置之后插入数据

这个操作我们就要简单的多,我们先进行断言,然后创建一个新的节点,让新的节点的next指向pos节点的下一个节点,再把pos的next指向新的节点。

3.9删除指定位置节点

老操作,先进行断言如果我们删除的是头节点,那么我们直接把头节点指向头节点的下一个节点,然后释放pos,返回无值就可以了;如果不是头节点,那么我们就要先创建一个prev指针来找pos的前一个节点,把prev的next指向pos的下一个,再释放掉pos就可以了,我们也可以为了规范再把pos置为NULL。

3.10删除pos之后的节点

这个就要节点的多,我们只需要断言一下pos和pos的下一个节点就行了,然后我们再创建一个del指针指向pos的下一个节点,然后把pos的next指向pos的下一个的下一个节点,我们再释放掉del。

3.11销毁链表

我们进行一下断言,再创建一个指针进行遍历,每遍历一次就销毁一个节点。

4 所有的代码

//SList.h头文件#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>//定义链表节点的结构
typedef int SLDataType;
typedef struct SListNode
{SLDataType data;//要保存的数据struct SListNode* next;
}SLNode;//创建几个节点组成一个链表,并打印链表
void SLNPrint(SLNode* phead);
//尾插
void SLPushBack(SLNode** pphead, SLDataType x);
//头插
void SLPushFront(SLNode** pphead, SLDataType x);
//删除
void SLPopBack(SLNode** pphead);
void SLPopFront(SLNode** pphead);SLNode* SLFind(SLNode** pphead, SLDataType x);//指定位置的插入和删除//在指定位置之前插入数据
void SLInsert(SLNode** pphead,SLNode* pos,SLDataType x);
//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x);//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos);
//删除pos之后节点
void SLEraseAfter(SLNode* pos);
//销毁链表
void SListDesTroy(SLNode** pphead);
//SList.c源文件#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"void SLNPrint(SLNode* phead)
{//循环打印SLNode* pcur = phead;while (pcur){printf("%d ->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}SLNode* SLBuyNode(SLDataType x)
{SLNode* node = (SLNode*)malloc(sizeof(SLNode));node->data = x;node->next = NULL;return node;
}//尾插
void SLPushBack(SLNode** pphead, SLDataType x)
{assert(pphead);SLNode* node = SLBuyNode(x);if (*pphead == NULL){*pphead = node;return;}//说明链表不为空,找尾SLNode* pcur = *pphead;while (pcur->next){pcur = pcur->next;}pcur->next = node;
}
//头插
void SLPushFront(SLNode** pphead, SLDataType x)
{assert(pphead);SLNode* node = SLBuyNode(x);//新节点跟头节点连接起来node->next = *pphead;//让新的节点成为头节点*pphead = node;
}
//删除
void SLPopBack(SLNode** pphead)
{assert(pphead);//第一个节点不能为空assert(*pphead);//只有一个节点的情况if ((*pphead)->next==NULL){//直接把头节点删除free(*pphead);*pphead = NULL;}else{//找尾节点和尾节点的前一个节点SLNode* prev = NULL;SLNode* ptail = *pphead;while (ptail->next != NULL){prev = ptail;ptail = ptail->next;}//prev的next指针不再指向ptail,指向下一个节点prev->next = ptail->next;free(ptail);ptail = NULL;}}
void SLPopFront(SLNode** pphead)
{assert(pphead);assert(*pphead);SLNode* del = *pphead;*pphead = (*pphead)->next;free(del);del = NULL;//出于代码规范
}SLNode* SLFind(SLNode** pphead, SLDataType x)
{assert(pphead);SLNode* pcur = *pphead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{assert(pphead);//约定链表不为空,pos也不能为空assert(pos);assert(*pphead);SLNode* node = SLBuyNode(x);//处理只有一个节点+pos指向第一个节点if (pos==*pphead){node->next = *pphead;*pphead = node;return;}//找pos的前一个节点SLNode* prev = *pphead;while(prev->next!=pos){prev = prev->next;}//prev node posnode->next = pos;prev->next = node;
}//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x)
{assert(pos);SLNode* node = SLBuyNode(x);node->next = pos->next;pos->next = node;
}//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(*pphead);assert(pos);if (pos == *pphead){*pphead = (*pphead)->next;free(pos);return;}//找pos的前一个节点SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;
}
//删除pos之后节点
void SLEraseAfter(SLNode* pos)
{assert(pos&&pos->next);SLNode* del = pos->next;pos->next = del->next;free(del);
}//销毁链表
void SListDesTroy(SLNode** pphead)
{assert(pphead);SLNode* pcur = *pphead;while (pcur){SLNode* next = pcur->next;free(pcur);pcur = next;}pcur = NULL;
}

相关文章:

手撕单链表(C语言)

目录 1.单链表的物理结构 2.头文件的实现 3.SList.c文件的实现 3.1尾插、创建节点 3.2打印 3.3头插 3.4尾删 3.5头删 3.6查找 3.7指定位置之前插入数据 3.8指定位置之后插入数据 3.9删除指定位置节点 3.10删除pos之后的节点 3.11销毁链表 4 所有的代码 1.单链表的物理结构 众所…...

60 权限提升-MYMSORA等SQL数据库提权

目录 数据库应用提权在权限提升中的意义WEB或本地环境如何探针数据库应用数据库提权权限用户密码收集等方法目前数据库提权对应的技术及方法等 演示案例Mysql数据库提权演示-脚本&MSF1.UDF提权知识点: (基于MYSQL调用命令执行函数&#xff09;读取数据库存储或备份文件 (了…...

【C++上层应用】2. 预处理器

文章目录 【 1. #define 预处理 】【 2. #ifdef、#if 条件编译 】2.1 #ifdef2.2 #if2.3 实例 【 3. # 和 ## 预处理 】3.1 # 替换预处理3.2 ## 连接预处理 【 4. 预定义宏 】 预处理器是一些指令&#xff0c;指示编译器在实际编译之前所需完成的预处理。 所有的预处理器指令都是…...

ISP--Black Level Correction(黑电平矫正)

图像的每一个像素点都是由一个光电二极管控制的&#xff0c;由二极管将电信号&#xff0c;转换为数字信号。 那么&#xff0c;我们知道了&#xff0c;图像的像素值是与电信号强度相关的。但是&#xff0c;我们得知道&#xff0c;每一个光电二极管要想工作&#xff0c;都得有一定…...

python项目源码基于django的宿舍管理系统dormitory+mysql数据库文件

基于Django的宿舍管理系统 运行效果 个人亲自制作python项目源码基于django的宿舍管理系统dormitorymysql数据库文件 1. 介绍 宿舍管理系统是一个基于Django框架开发的项目&#xff0c;旨在简化和优化宿舍管理的流程。该系统包括学生和管理员两个角色&#xff0c;学生可以通过…...

Java和 JS 的10大不同之处,你清楚吗?

还记得刚开始学习编程时&#xff0c;我就在想&#xff1a;“Java和JavaScript是同一种语言吗&#xff1f;”。就是因为看到它们名称中都带“java”&#xff0c;所以才会误以为它们有关系。实际上&#xff0c;它们并没有太大的联系。 这两者的关系&#xff0c;就和英语与斯瓦希…...

vue动态配置路由

文章目录 前言定义项目页面格式一、vite 配置动态路由新建 /router/utils.ts引入 /router/utils.ts 二、webpack 配置动态路由总结如有启发&#xff0c;可点赞收藏哟~ 前言 项目中动态配置路由可以减少路由配置时间&#xff0c;并可减少配置路由出现的一些奇奇怪怪的问题 路由…...

科技云报道:全球勒索攻击创历史新高,如何建立网络安全的防线?

科技云报道原创。 最简单的方式&#xff0c;往往是最有效的&#xff0c;勒索软件攻击就属于这类。 近两年&#xff0c;随着人类社会加速向数字世界进化&#xff0c;勒索软件攻击成为网络安全最为严重的威胁之一。今年以来&#xff0c;勒索软件攻击在全球范围内呈现快速上升态…...

通过bat命令启动jar后缀软件

要通过bat命令启动一个带有.jar后缀的软件&#xff0c;可以使用以下的bat文件命令&#xff1a; echo off java -jar "路径\文件名.jar" pause请将路径\文件名.jar替换为实际的文件路径和文件名。例如&#xff0c;如果你的文件位于C:\Program Files\MyApp\app.jar&am…...

Python选择排序和冒泡排序算法

选择排序和冒泡排序都是常见的排序算法。以下是这两种算法的Python实现&#xff1a; 选择排序&#xff08;Selection Sort&#xff09; 选择排序的基本思想是在未排序的序列中找到最小&#xff08;或最大&#xff09;元素&#xff0c;存放到排序序列的起始位置&#xff0c;然…...

集合的自反关系和对称关系

集合的自反关系和对称关系 一&#xff1a;集合的自反关系1&#xff1a;原理&#xff1a;2&#xff1a;代码实现 二&#xff1a;对称关系1&#xff1a;原理&#xff1a;2&#xff1a;代码实现 三&#xff1a;总结 一&#xff1a;集合的自反关系 1&#xff1a;原理&#xff1a; …...

传递函数的推导和理解

传递函数的推导和理解 假设有一个线性系统&#xff0c;在一般情况下&#xff0c;它的激励 x ( t ) x(t) x(t)与响应 y ( t ) y(t) y(t)所满足的的关系&#xff0c;可用下列微分方程来表示&#xff1a; a n y ( n ) a n − 1 y ( n − 1 ) a n − 2 y ( n − 2 ) ⋯ a 1 y…...

STM32 SPI

SPI介绍 SPI是Serial Pepheral interface缩写&#xff0c;串行外围设备接口。 SPI接口是一种高速的全双工同步通信总线&#xff0c;已经广泛应用在众多MCU、存储芯片、AD转换器和LCD之间。大部分STM32有3个SPI接口&#xff0c;本实验使用的是SPI1。 SPI同一时刻既能发送数据&…...

Linux系统编程 day02 vim、gcc、库的制作与使用

Linux系统编程 day02 vim、gcc、库的制作与使用 01. vim0101. 命令模式下的操作0102. 切换到文本输入模式0103. 末行模式下的操作0104. vim的配置文件 02. gcc03. 库的制作与使用0301. 静态库的制作与使用0302. 动态库(共享库)的制作与使用 01. vim vim是一个编辑器&#xff0…...

Mistral 7B 比Llama 2更好的开源大模型 (四)

Mistral 7B在平衡高性能和保持大型语言模型高效的目标方面迈出了重要的一步。通过我们的工作,我们的目标是帮助社区创建更实惠、更高效、更高性能的语言模型,这些模型可以在广泛的现实世界应用程序中使用。 Mistral 7B在实践中,对于16K和W=4096的序列长度,对FlashAttentio…...

相似基因序列问题 ——查找

【题目背景】 生物的遗传物质存在个体间或种群水平的差异&#xff0c;这样的差异被称为遗传变异。突变及基因重组等因素都会导致遗传变异。尽管亲代在将其遗传信息传递给子代时会发生遗传变异&#xff0c;但是这些遗传变异仅占遗传物质的一小部分&#xff0c;通常亲代和子代之…...

【汇编】“转移”综述、操作符offset、jmp指令

文章目录 前言一、转移综述1.1 :背景&#xff1a;1.2 转移指令1.3 转移指令的分类按转移行为根据指令对IP修改的范围不同 二、操作符offset2.1 offset操作符是干什么的&#xff1f;标号是什么&#xff1f; 2.2 nop是什么&#xff1f; 三、jmp指令3.1 jmp指令的功能3.2 jmp指令&…...

Java格式化类Format

文章目录 Format介绍Format方法- format&#xff08;格式化&#xff09;- parseObject&#xff08;解析&#xff09; 格式化分类日期时间格式化1. DateFormat常用方法getInstancegetDateInstancegetTimeInstancegetDateTimeInstance 方法入参styleLocale 2. SimpleDateFormat常…...

力扣每日一题-美化数组的最少删除数-2023.11.21

力扣每日一题&#xff1a;美化数组的最少删除数 开篇 今天的力扣每日一题居然写出来了&#xff0c;好开心&#xff0c;迫不及待地把题目分享出来&#xff0c;希望你也能把它狠狠拿下。 题目链接: 2216.美化数组的最少删除数 题目描述 代码思路 创建一个list集合来保存数组&a…...

【练习】检测U盘并自动复制内容到电脑的软件

软件作用&#xff1a; 有U盘插在电脑上后&#xff0c;程序会检测到U盘的路径。 自己可以提前设置一个保存复制文件的路径或者使用为默认保存的复制路径&#xff08;默认为桌面&#xff0c;可自行修改&#xff09;。 检测到U盘后程序就会把U盘的文件复制到电脑对应的…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

Java - Mysql数据类型对应

Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 &#xff08;一&#xff09;引用计数法 &#xff08;二&#xff09;可达性分析算法 二、垃圾回收算法 &#xff08;一&#xff09;标记清除 &#xff08;二&#xff09;标记整理 &#xff08;三&#xff09;复制 &#xff08;四&#xff…...

抽象类和接口(全)

一、抽象类 1.概念&#xff1a;如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象&#xff0c;这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法&#xff0c;包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中&#xff0c;⼀个类如果被 abs…...

【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?

FTP&#xff08;File Transfer Protocol&#xff09;本身是一个基于 TCP 的协议&#xff0c;理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况&#xff0c;主要原因包括&#xff1a; ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...

GraphRAG优化新思路-开源的ROGRAG框架

目前的如微软开源的GraphRAG的工作流程都较为复杂&#xff0c;难以孤立地评估各个组件的贡献&#xff0c;传统的检索方法在处理复杂推理任务时可能不够有效&#xff0c;特别是在需要理解实体间关系或多跳知识的情况下。先说结论&#xff0c;看完后感觉这个框架性能上不会比Grap…...

HTTPS证书一年多少钱?

HTTPS证书作为保障网站数据传输安全的重要工具&#xff0c;成为众多网站运营者的必备选择。然而&#xff0c;面对市场上种类繁多的HTTPS证书&#xff0c;其一年费用究竟是多少&#xff0c;又受哪些因素影响呢&#xff1f; 首先&#xff0c;HTTPS证书通常在PinTrust这样的专业平…...