当前位置: 首页 > 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盘的文件复制到电脑对应的…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

如何在网页里填写 PDF 表格?

有时候&#xff0c;你可能希望用户能在你的网站上填写 PDF 表单。然而&#xff0c;这件事并不简单&#xff0c;因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件&#xff0c;但原生并不支持编辑或填写它们。更糟的是&#xff0c;如果你想收集表单数据&#xff…...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...

聚六亚甲基单胍盐酸盐市场深度解析:现状、挑战与机遇

根据 QYResearch 发布的市场报告显示&#xff0c;全球市场规模预计在 2031 年达到 9848 万美元&#xff0c;2025 - 2031 年期间年复合增长率&#xff08;CAGR&#xff09;为 3.7%。在竞争格局上&#xff0c;市场集中度较高&#xff0c;2024 年全球前十强厂商占据约 74.0% 的市场…...

深入解析光敏传感技术:嵌入式仿真平台如何重塑电子工程教学

一、光敏传感技术的物理本质与系统级实现挑战 光敏电阻作为经典的光电传感器件&#xff0c;其工作原理根植于半导体材料的光电导效应。当入射光子能量超过材料带隙宽度时&#xff0c;价带电子受激发跃迁至导带&#xff0c;形成电子-空穴对&#xff0c;导致材料电导率显著提升。…...

【java】【服务器】线程上下文丢失 是指什么

目录 ■前言 ■正文开始 线程上下文的核心组成部分 为什么会出现上下文丢失&#xff1f; 直观示例说明 为什么上下文如此重要&#xff1f; 解决上下文丢失的关键 总结 ■如果我想在servlet中使用线程&#xff0c;代码应该如何实现 推荐方案&#xff1a;使用 ManagedE…...