笔记六:单链表链表介绍与模拟实现
在他一生中,从来没有人能够像你们这样,以他的视角看待这个世界。
---------《寻找天堂》
目录
文章目录
一、什么是链表?
二、为什么要使用链表?
三、 单链表介绍与使用
3.1 单链表
3.1.1 创建单链表节点
3.1.2 单链表的头插、尾插
单链表的尾插
单链表的头插
3.1.3 单链表的头删、尾删
单链表的尾删编辑
单链表的头删
3.1.4 链表节点的查找
3.1.5 在指定位置插入数据
在指定位置前插入数据
在指定位置之后插入数据
3.1.6 删除pos之后的节点
3.1.7 销毁链表
四、完整代码
SList.h
SList.c
一、什么是链表?
链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构。链表由一系列节点(链表中每一个元素称为节点)组成,节点在运行时动态生成 (malloc),每个节点包括两个部分:
- 一个是存储数据元素的数据域
- 另一个是存储下一个节点地址的指针域
二、为什么要使用链表?
使用链表用于解决动态数量的数据存储。比如说,我们要管理一堆票据,可能有一张,但也可能有一亿张。怎么办呢?申请一个几十G的大数组存储着吗?万一用户只有一百张票据要处理呢?内存较小拒绝运行?可申请少了又不够用啊……
而且,用数组的话,删除然后添加票据,是每次删除让后面五百万张往前移一格呢、还是每次添加从头搜索空闲格子?如何区分空闲格子和值为0的数据?要进行区分的话是多占用空间呢还是占用数据值域?占用了值域会不会使得数据处理变得格外复杂?会不会一不小心就和正常数据混淆?万一拿来区分的字段在某个版本后废弃不用、或者扩充值域了呢?
那么,链表这种东西就是个很有效的数据结构,可以很有效的管理这类不定量的数据。
三、 单链表介绍与使用
3.1 单链表
单链表的每个节点除了存放数据元素外,还要存储指向下一个节点的指针。与顺序表进行对比:
| 优点 | 缺点 | |
| 顺序表 | 可随机存储,存储密度较高 | 要求大片连续空间,改变容量不方便 |
| 单链表 | 不要求大片连续空间,改变容量方便 | 不可随机存取,要耗费一定空间存放指针 |

3.1.1 创建单链表节点
typedef int SLTDataType;
//链表是由节点构成
//逻辑结构:一定连续;物理结构:不一定连续
//不会造成空间的浪费,由独立的节点构成
typedef struct SListNode { //SList : single linked list 单链表SLTDataType data; // 一个是存储数据元素的数据域struct SListNode* next; // 另一个是存储下一个节点地址的指针域
}SLTNode;

上面的结构体仅是单链表节点的定义,要创建一新的节点需要对里面的数据进行初始化:插入数值,节点指向的下一个节点为空
//创建一个节点
struct SListNode* SLTBuyNode(SLTDataType x) {SLTNode* newnode = (struct SListNode*)malloc(sizeof(struct SListNode));newnode->data = x;newnode->next = NULL;return newnode;
}

3.1.2 单链表的头插、尾插
单链表的尾插

这里传递的链表节点的参数,为什么是二级指针呢?
在初始化过程中,需要修改头指针,因此要用到二级指针传递头指针的地址,这样才能修改头指针。这与普通变量类似,当需要修改普通变量的值,需传递其地址。使用二级指针,很方便就修改了传入的结点一级指针的值。 如果用一级指针,则只能通过指针修改指针所指内容,却无法修改指针的值,也就是指针所指的内存块。
//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x) { //一级指针传递的为结构体变量地址的值,二级指针接收的是指向结构体变量的指针的地址assert(pphead);SLTNode* newnode = SLTBuyNode(x);//链表为空,新节点作为ppheadif (*pphead == NULL) {*pphead = newnode;return;}//链表不为空,链表的尾指向新节点SLTNode* ptail = *pphead;while (ptail->next) {ptail = ptail->next;}ptail->next = newnode;
}
单链表的头插

void SLTPushFront(SLTNode** pphead, SLTDataType x) {assert(pphead);//链表为空,新节点指向pphead,pphead再指向新节点;链表不为空,新节点指向pphead,pphead再指向新节点SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}
插入完数据后,想要打印以一下链表的数据,通过从头开始一个一个节点地访问数据,并打印,便实现了链表数据的打印。然后看看插入的情况;
//打印链表
void SLTPrint(SLTNode* phead) {SLTNode* pcur = phead;while (pcur) {printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}
尾插部分数据,观察打印的结果是否符合预期结果;发现运行结果与预期相符,所以链表的插入操作是没什么大问题的

3.1.3 单链表的头删、尾删
如果链表为空,不需要删除;如果删除的是第一个结点,则需要将保存链表首地址的指针保存第一个结点的下一个结点的 地址 如果删除的是中间结点,则找到中间结点的前一个结点,让前一个结点的指针域保存这个结 点的后一个结点的地址即可
单链表的尾删
void SLTPopBack(SLTNode** pphead) {assert(pphead);//链表为空,不删除assert(*pphead); //指向第一个节点的地址//链表不为空,只有一个节点if ((*pphead)->next = NULL) {free(*pphead);*pphead = NULL;return;}//多个节点,释放最后一个节点,其前驱节点指向空SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next) {prev = ptail;ptail = ptail->next;}prev->next = NULL;free(ptail);ptail = NULL;
}
单链表的头删

void SLTPopFront(SLTNode** pphead) {assert(pphead);//链表为空,不删除assert(*pphead);//链表不为空,释放最后一个节点,其前驱节点指向空SLTNode* pcur = *pphead;*pphead = pcur->next;pcur->next = NULL;free(pcur);pcur = NULL;
}
3.1.4 链表节点的查找
先对比第一个结点的数据域是否是想要的数据,如果是就直接返回,如果不是则继续查找下 一个结点,如果到达最后一个结点的时候都没有匹配的数据,说明要查找数据不存在
//查找
SLTNode* STLFind(SLTNode** pphead, SLTDataType x) {assert(pphead);SLTNode* pcur = *pphead;while (pcur) {if (pcur->data == x) {return pcur;}pcur = pcur->next;}return NULL;
}
3.1.5 在指定位置插入数据
在指定位置前插入数据
//在指定位置前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {assert(pphead);assert(pos);//头节点不能为空assert(*pphead);SLTNode* newnode = SLTBuyNode(x);//当pos节点指向 头节点时,进行头插if (*pphead == pos) {SLTPushFront(pphead, x);return;}//pos pphead不指向同一节点SLTNode* prev = *pphead;while (prev->next != pos) { //找到pos节点的前驱prev = prev->next;}newnode->next = pos;prev->next = newnode;
}
在指定位置之后插入数据
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}
3.1.6 删除pos之后的节点
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos) {assert(pos);assert(pos->next);//pos后不为空SLTNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}
3.1.7 销毁链表
重新定义一个指针next,保存pcur指向节点的地址,然后next后移保存下一个节点的地址,然后释放pcur对应的节点,以此类推,直到pcur为NULL为止
//销毁链表,由独立的节点构成,需要一个个销毁
void SListDestroy(SLTNode** pphead) {assert(pphead);assert(*pphead);SLTNode* pcur = *pphead;while (pcur) {SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}
四、完整代码
SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>//顺序表存在的一定的问题:
//1.顺序表的中间/头部的插入需要挪动数据
//2.扩容存在性能的消耗
//3.会有空间的浪费typedef int SLTDataType;
//链表是由节点构成
//逻辑结构:一定连续;物理结构:不一定连续
//不会造成空间的浪费,由独立的节点构成
typedef struct SListNode { //SList : single linked list 单链表SLTDataType data;struct SListNode* next;
}SLTNode;//链表的头插、尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(struct SListNode** pphead, SLTDataType x);//链表的头删、尾删
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);//打印链表
void SLTPrint(SLTNode* phead);//查找
SLTNode* STLFind(SLTNode** phead, SLTDataType x);//在指定位置前插入数据
void SLTInsert(SLTNode** phead, SLTNode* pos,SLTDataType x);//删除pos节点
void SLTErase(SLTNode** phead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** phead);
SList.c
#include"SList.h"//创建一个节点
struct SListNode* SLTBuyNode(SLTDataType x) {SLTNode* newnode = (struct SListNode*)malloc(sizeof(struct SListNode));newnode->data = x;newnode->next = NULL;return newnode;
}//打印链表
void SLTPrint(SLTNode* phead) {SLTNode* pcur = phead;while (pcur) {printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}//链表的头插、尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x) { //一级指针传递的为结构体变量地址的值,二级指针接收的是指向结构体变量的指针的地址assert(pphead);SLTNode* newnode = SLTBuyNode(x);//链表为空,新节点作为ppheadif (*pphead == NULL) {*pphead = newnode;return;}//链表不为空,链表的尾指向新节点SLTNode* ptail = *pphead;while (ptail->next) {ptail = ptail->next;}ptail->next = newnode;
}void SLTPushFront(SLTNode** pphead, SLTDataType x) {assert(pphead);//链表为空,新节点指向pphead,pphead再指向新节点;链表不为空,新节点指向pphead,pphead再指向新节点SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}//链表的头删、尾删
void SLTPopBack(SLTNode** pphead) {assert(pphead);//链表为空,不删除assert(*pphead); //指向第一个节点的地址//链表不为空,只有一个节点if ((*pphead)->next = NULL) {free(*pphead);*pphead = NULL;return;}//多个节点,释放最后一个节点,其前驱节点指向空SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next) {prev = ptail;ptail = ptail->next;}prev->next = NULL;free(ptail);ptail = NULL;
}
void SLTPopFront(SLTNode** pphead) {assert(pphead);//链表为空,不删除assert(*pphead);//链表不为空,释放最后一个节点,其前驱节点指向空SLTNode* pcur = *pphead;*pphead = pcur->next;pcur->next = NULL;free(pcur);pcur = NULL;
}//查找
SLTNode* STLFind(SLTNode** pphead, SLTDataType x) {assert(pphead);SLTNode* pcur = *pphead;while (pcur) {if (pcur->data == x) {return pcur;}pcur = pcur->next;}return NULL;
}//在指定位置前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {assert(pphead);assert(pos);//头节点不能为空assert(*pphead);SLTNode* newnode = SLTBuyNode(x);//当pos节点指向 头节点时,进行头插if (*pphead == pos) {SLTPushFront(pphead, x);return;}//pos pphead不指向同一节点SLTNode* prev = *pphead;while (prev->next != pos) { //找到pos节点的前驱prev = prev->next;}newnode->next = pos;prev->next = newnode;
}//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos) {assert(pphead);assert(pos);//当pos节点指向 头节点时,进行头删if (*pphead == pos) {SLTPopFront(pphead);return;}//pos pphead不指向同一节点SLTNode* prev = *pphead;while (prev->next != pos) { //找到pos节点的前驱prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;
}//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos) {assert(pos);assert(pos->next);//pos后不为空SLTNode* del = pos->next;pos->next = pos->next->next;//pos->next = del->next;free(del);del = NULL;
}//销毁链表,由独立的节点构成,需要一个个销毁
void SListDestroy(SLTNode** pphead) {assert(pphead);assert(*pphead);SLTNode* pcur = *pphead;while (pcur) {SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}
相关文章:
笔记六:单链表链表介绍与模拟实现
在他一生中,从来没有人能够像你们这样,以他的视角看待这个世界。 ---------《寻找天堂》 目录 文章目录 一、什么是链表? 二、为什么要使用链表? 三、 单链表介绍与使用 3.1 单链表 3.1.1 创建单链表节点 3.1.2 单链表的头插、…...
坐落于杭州的电商代运营公司品融电商
坐落于杭州的电商代运营公司品融电商 在中国电商行业蓬勃发展的浪潮中,品融电商(PINKROON)作为一家扎根杭州的新锐品牌管理公司,凭借其独特的全域增长方法论和实战经验,迅速崛起为行业标杆。自2020年成立以来&#x…...
微前端之 Garfish.js 的基础使用教程和进阶配置
前言 在现代前端开发中,微前端架构逐渐成为一种流行的解决方案。它允许将大型应用拆分成多个小型独立的子应用,从而提高开发效率和可维护性。Garfish.js 是一个强大的微前端框架,可以帮助我们轻松实现这一架构。在本文中,通过一个…...
图像的特征
图像的特征主要包括以下几类: 1. 颜色特征: 直方图:描述图像中颜色的分布。 颜色矩:通过颜色的均值、方差等统计量表示颜色分布。 主色调:图像中占主导地位的颜色。 2. 纹理特征: 灰度共生矩阵࿰…...
Spring上下文工具类
文章目录 获取ip地址请求上下文相关Spring上下文获取Bean对象 获取ip地址 public class IpUtils {private IpUtils() {}/*** 获取请求ip地址** return {link String}*/public static String getIpAddress() {HttpServletRequest request RequestContextHolderUtils.getReques…...
JSONUtil InvocationTargetException: null
使用JSONUtil时候报错,一般这时候你检查下自己代码里是不是重写了get或者set InvocationTargetException 是 Java 中的一个异常,通常在使用反射(Reflection)或动态代理(Dynamic Proxy)时抛出。它表示在调用…...
高压为什么cover不住低压的hold问题
常规下我们认为hold问题常发生在高压下,但很多情况下高压的hold无法cover低压的hold hold slack (tlaunch -tcapture) (tcqtcomb) -thold -tuncertainty (tlaunch -tcapture):代表时钟skew (tcqtcomb):代表data path的长度 thold:代表查表…...
【算法学习之路】8.栈和队列
栈和队列 前言一.简介二.题目12 前言 我会将一些常用的算法以及对应的题单给写完,形成一套完整的算法体系,以及大量的各个难度的题目,目前算法也写了几篇,题单正在更新,其他的也会陆陆续续的更新,希望大家点…...
OpenMCU(三):STM32F103 FreeRTOS移植
概述 本文主要描述了STM32F103移植FreeRTOS的简要步骤。移植描述过程中,忽略了Keil软件的部分使用技巧。默认读者熟练使用Keil软件。本文的描述是基于OpenMCU_RTOS这个工程,该工程已经下载放好了移植STM32F103 FreeRTOS的所有文件 OpenMCU_RTOS工程的愿景…...
大数据 spark hive 总结
Apache Spark 简介 是一个开源的统一分析引擎,专为大规模数据处理而设计。它提供了高级API,支持Java、Scala、Python和R语言,并且包含了一个优化过的执行引擎,该引擎支持循环计算(如机器学习算法)和交互式…...
小程序开发总结
今年第一次帮别人做小程序。 从开始动手到完成上线,一共耗时两天。AI 让写代码变得简单、高效。 不过,小程序和 Flutter 等大厂开发框架差距实在太大,导致我一开始根本找不到感觉。 第一,IDE 不好用,各种功能杂糅在…...
QLoggingCategory类使用
QLoggingCategory类使用 QLoggingCategory的概述 QLoggingCategory是Qt的日志策略类;可以通过声明不同的日志策略对象来输出不同的日志信息。打印信息类型如下:宏 Q_DECLARE_LOGGING_CATEGORY(name) 定义一个返回QLoggingCategory对象函数,…...
GPU加速生信分析-宏基因组MAG去污染
Deepurify利用多模态深度语言模型来过滤污染的基因组,从而提高了宏基因组组装基因组(MAGs)的质量,并且可以利用GPU加速。 宏基因组组装的基因组 (MAG) 为使用宏基因组测序数据探索微生物暗物质提供了有价值…...
数据结构(蓝桥杯常考点)
数据结构 前言:这个是针对于蓝桥杯竞赛常考的数据结构内容,基础算法比如高精度这些会在下期给大家总结 数据结构 竞赛中,时间复杂度不能超过10的7次方(1秒)到10的8次方(2秒) 空间限制&#x…...
从0到1入门Linux
一、常用命令 ls 列出目录内容 cd切换目录mkdir创建新目录rm删除文件或目录cp复制文件或目录mv移动或重命名文件和目录cat查看文件内容grep在文件中查找指定字符串ps查看当前进程状态top查看内存kill终止进程df -h查看磁盘空间存储情况iotop -o直接查看比较高的磁盘读写程序up…...
灰色地带规避:知识产权校验API的商标库模糊匹配算法
在反向海淘或其他电商业务场景中,为了规避知识产权方面的灰色地带,开发知识产权校验 API 并运用商标库模糊匹配算法是很有必要的。以下将详细介绍商标库模糊匹配算法的设计与实现: 算法设计思路 商标库模糊匹配算法的核心目标是在给定一个待匹…...
React:类组件(中)
dangerouslySetInnerHTML React写进{}内的东西,不允许被当作代码块解析,是为了防止xss攻击和代码注入 XSS(跨站脚本攻击,Cross-Site Scripting) 是一种常见的安全漏洞,攻击者通过注入恶意脚本到网页中&…...
第六次CCF-CSP认证(含C++源码)
第六次CCF-CSP认证 数位之和(easy)思路及AC代码遇到的问题 开心消消乐(easy)思路及AC代码 画图(mid)思路及AC代码 数位之和(easy) 题目链接 思路及AC代码 既然题目要求我们输出各位…...
SpringBoot 如何调用 WebService 接口
前言 调用WebService接口的方式有很多,今天记录一下,使用 Spring Web Services 调用 SOAP WebService接口 一.导入依赖 <!-- Spring Boot Web依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId…...
算法 之 树形dp 树的中心、重心
文章目录 重心实践题目小红的陡峭值 在树的算法中,求解树的中心和重心是一类十分重要的算法 求解树的重心 树的重心的定义:重心是树中的一个节点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点…...
Docker 配置镜像源
》》Daemon {"registry-mirrors": ["https://docker.1ms.run","https://docker.xuanyuan.me"] }》》》然后在重新 docker systemctl restart docker...
Linux 离线部署Ollama和DeepSeek-r1模型
都在复制粘贴联网状态下linux部署deepseek,离线状态下需要下载Ollama和DeepSeek模型,然后将下载包上传到linux中。 1、下载Ollama https://github.com/ollama/ollama/releases 注意:如果CentOS7建议安装V0.5.11版本,V0.5.13需要…...
SQLAlchemy系列教程:如何执行原生SQL
Python中的数据库交互提供了高级API。但是,有时您可能需要执行原始SQL以提高效率或利用数据库特定的特性。本指南介绍在SQLAlchemy框架内执行原始SQL。 在SQLAlchemy中执行原生SQL SQLAlchemy虽然以其对象-关系映射(ORM)功能而闻名ÿ…...
RuleOS:区块链开发的“新引擎”,点燃Web3创新之火
RuleOS:区块链开发的“新引擎”,点燃Web3创新之火 在区块链技术的浪潮中,RuleOS宛如一台强劲的“新引擎”,为个人和企业开发去中心化应用(DApp)注入了前所未有的动力。它以独特的设计理念和强大的功能特性&…...
【编译器】VSCODE烧录ESP32-C3——xiaozhi智能聊天机器人固件
【编译器】VSCODE烧录ESP32-C3——xiaozhi智能聊天机器人固件 文章目录 [TOC](文章目录) 前言一、方法一:使用固件烧录工具1. 安装CH340驱动2. 打开FLASH_DOWNLOAD文件3. 选择芯片类型和烧录方式4. 选择烧录文件5. 参数配置 二、方法二:VSCODE导入工程1.…...
设计模式文章汇总-Golang语言实现
Golang学习笔记_27——单例模式 Golang学习笔记_28——工厂方法模式 Golang学习笔记_29——抽象工厂模式 Golang学习笔记_30——建造者模式 Golang学习笔记_31——原型模式 Golang学习笔记_32——适配器模式 Golang学习笔记_33——桥接模式 Golang学习笔记_34——组合模式 Gola…...
显式 GC 的使用:留与去,如何选择?
目录 一、什么是显式 GC? (一) 垃圾回收的基本原理 (二)显式 GC 方法和行为 1. System.gc() 方法 2. 显式 GC 的行为 (三)显式 GC 的使用场景与风险 1. JVM 如何处理显式 GC 2. 显式 GC…...
SpringMVC概述以及入门案例
目录 SpringMVC概述 为什么需要Spring MVC? SpringMVC入门 工作流程分析 SpringMVC概述 SpringMVC技术与Servlet技术功能等同,均属于Web层开发技术。SpringMVC是一种基于java实现MVC模型的轻量级Web框架。 为什么需要Spring MVC? 在传统J…...
5. 前后端实现文件上传与解析
1. 说明 在实际开发中,比较常见的一个功能是需要在前端页面中选择系统中的某个文件上传到服务器中进行解析,解析后的文件内容可以用来在服务器中当作参数,或者传递给其它组件使用,或者需要存储到数据库中。所以本文就提供一种方式…...
⭐LeetCode周赛 3468. 可行数组的数目——暴力与数学⭐
⭐LeetCode周赛 3468. 可行数组的数目——暴力与数学⭐ 示例 1: 输入:original [1,2,3,4], bounds [[1,2],[2,3],[3,4],[4,5]] 输出:2 解释: 可能的数组为: [1, 2, 3, 4] [2, 3, 4, 5] 示例 2: 输入&…...
