笔记六:单链表链表介绍与模拟实现

在他一生中,从来没有人能够像你们这样,以他的视角看待这个世界。

                                                                                                             ---------《寻找天堂》    

目录

文章目录

一、什么是链表?

二、为什么要使用链表?

三、 单链表介绍与使用

        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;
}

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

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

相关文章

尚硅谷爬虫note15n

1. 多条管道 多条管道开启&#xff08;2步&#xff09;&#xff1a; (1)定义管道类 &#xff08;2&#xff09;在settings中开启管道 在pipelines中&#xff1a; import urllib.request # 多条管道开启 #(1)定义管道类 #&#xff08;2&#xff09;在setti…

oracle检查字段为空

在Oracle数据库中&#xff0c;检查字段是否为空通常涉及到使用IS NULL条件。如果你想查询某个表中的字段是否为空&#xff0c;你可以使用SELECT语句结合WHERE子句来实现。这里有一些基本示例来展示如何进行这样的查询。 示例1: 检查单个字段是否为空 假设你有一个表employees…

虚幻基础:动画层接口

文章目录 动画层&#xff1a;动画图表中的函数接口&#xff1a;名字&#xff0c;没有实现。动画层接口&#xff1a;由动画蓝图实现1.动画层可直接调用实现功能2.动画层接口必须安装3.动画层默认使用本身实现4.动画层也可使用其他动画蓝图实现&#xff0c;但必须在角色蓝图中关联…

HarmonyOS学习第18天:多媒体功能全解析

一、开篇引入 在当今数字化时代&#xff0c;多媒体已经深度融入我们的日常生活。无论是在工作中通过视频会议进行沟通协作&#xff0c;还是在学习时借助在线课程的音频讲解加深理解&#xff0c;亦或是在休闲时光用手机播放音乐放松身心、观看视频打发时间&#xff0c;多媒体功…

绪论数据结构基本概念(刷题笔记)

&#xff08;一&#xff09;单选题 1.与数据元素本身的形式、相对位置和个数无关的是&#xff08;B&#xff09;【广东工业大学2019年829数据结构】 A.数据存储结构 B.数据逻辑结构 C.算法 D.操作 2.在数据结构的讨论中把数据结构从逻辑上分为&#xff08;C&#xff09;【中国…

GPTQ - 生成式预训练 Transformer 的精确训练后压缩

GPTQ - 生成式预训练 Transformer 的精确训练后压缩 flyfish 曾经是 https://github.com/AutoGPTQ/AutoGPTQ 现在是https://github.com/ModelCloud/GPTQModel 对应论文是 《Accurate Post-Training Quantization for Generative Pre-trained Transformers》 生成式预训练Tr…

git的使用方法

文章目录 前言git简介GIT的基本操作克隆仓库 (Clone)获取最新代码 (Pull)提交代码到远程仓库查看当前分支查看提交代码的日志git config 配置用户信息 GIT的实操 前言 git是一种软件版本管理工具&#xff0c;在多人团队软件开发中地方非常重要。 类似与SVN&#xff0c;git工具…

php虚拟站点提示No input file specified时的问题及权限处理方法

访问站点&#xff0c;提示如下 No input file specified. 可能是文件权限有问题&#xff0c;也可能是“.user.ini”文件路径没有配置对&#xff0c;最简单的办法就是直接将它删除掉&#xff0c;还有就是将它设置正确 #配置成自己服务器上正确的路径 open_basedir/mnt/qiy/te…

使用Langflow和AstraDB构建AI助手:从架构设计到与NocoBase的集成

本文由 Leandro Martins 编写&#xff0c;最初发布于 Building an AI Assistant with Langflow and AstraDB: From Architecture to Integration with NocoBase。 引言 本文的目标是演示如何创建一个集成了 NocoBase、LangFlow 和 VectorDB 工具的 AI 助手。作为基础&#xf…

6.聊天室环境安装 - Ubuntu22.04 - elasticsearch(es)的安装和使用

目录 介绍安装安装kibana安装ES客户端使用 介绍 Elasticsearch&#xff0c; 简称 ES&#xff0c;它是个开源分布式搜索引擎&#xff0c;它的特点有&#xff1a;分布式&#xff0c;零配置&#xff0c;自动发现&#xff0c;索引自动分片&#xff0c;索引副本机制&#xff0c;res…

SSL VXN

SSL VPN是采用SSL&#xff08;Security Socket Layer&#xff09;/TLS&#xff08;Transport Layer Security&#xff09;协议来实现远程接入的一种轻量级VPN技术,其基于B/S架构&#xff0c;免于安装客户端&#xff0c;相较与IPSEC有更高的灵活度和管理性&#xff0c;当隧道建立…

【Qt】成员函数指针

一、成员函数指针的本质 与普通函数指针的区别&#xff1a; // 普通函数指针 void (*funcPtr)() &普通函数;// 成员函数指针 void (MyClass::*memberFuncPtr)() &MyClass::成员函数;• 绑定对象&#xff1a;成员函数指针必须与类的实例对象结合使用 • 隐含 this 指…

通义万相2.1开源版本地化部署攻略,生成视频再填利器

2025 年 2 月 25 日晚上 11&#xff1a;00 通义万相 2.1 开源发布&#xff0c;前两周太忙没空搞它&#xff0c;这个周末&#xff0c;也来本地化部署一个&#xff0c;体验生成效果如何&#xff0c;总的来说&#xff0c;它在国内文生视频、图生视频的行列处于领先位置&#xff0c…

Linux——system V共享内存

共享内存区是最快的IPC(进程内通信)形式&#xff0c;不再通过执行进入内核的系统调用来传递彼此的数据 1.共享内存的原理 IPC通信的本质是让不同的进程先看到同一份资源&#xff0c;然后再进行通信&#xff0c;所以想要通过共享内存进行通信&#xff0c;那么第一步一定是让两个…

01 SQl注入基础步骤(数字、字符、布尔盲注、报错)

目录 1、SQL注入漏洞的概要 2、SQL注入的常规思路 3、数字型注入 4、字符型注入 5、布尔盲注 6、报错注入 1、SQL注入漏洞的概要 原理&#xff1a;通过用户输入的数据未严格过滤&#xff0c;将恶意SQL语句拼接到原始查询中&#xff0c;从而操控数据库执行非预期操作。 …

leetcode-sql数据库面试题冲刺(高频SQL五十题)

题目&#xff1a; 620.有趣的电影 表&#xff1a;cinema ------------------------ | Column Name | Type | ------------------------ | id | int | | movie | varchar | | description | varchar | | rating | float | ------------------------ id 是该表的主键(具有唯一值…

7.2 奇异值分解的基与矩阵

一、奇异值分解 奇异值分解&#xff08;SVD&#xff09;是线性代数的高光时刻。 A A A 是一个 m n m\times n mn 的矩阵&#xff0c;可以是方阵或者长方形矩阵&#xff0c;秩为 r r r。我们要对角化 A A A&#xff0c;但并不是把它化成 X − 1 A X X^{-1}A X X−1AX 的形…

在本地部署DeepSeek等大模型时,需警惕的潜在安全风险

在本地部署DeepSeek等大模型时&#xff0c;尽管数据存储在本地环境&#xff08;而非云端&#xff09;&#xff0c;但仍需警惕以下潜在安全风险&#xff1a; 1. 模型与数据存储风险 未加密的存储介质&#xff1a;若训练数据、模型权重或日志以明文形式存储&#xff0c;可能被物…

【javaEE】多线程(进阶)

1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; Hello, Hello~ 亲爱的朋友们&#x1f44b;&#x1f44b;&#xff0c;这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章&#xff0c;请别吝啬你的点赞❤️❤️和收藏&#x1f4d6;&#x1f4d6;。如果你对我的…

dify中使用NL2SQL

在 Dify 工作流中融入 NL2SQL&#xff08;自然语言转 SQL&#xff09;之能力&#xff0c;可依循如下步骤达成&#xff0c;借由 Dify 的模块化设计以及模型编排之功能&#xff0c;优化数据库查询之智能化交互&#xff1a; 一、环境准备与 Dify 部署 安装 Docker 与 Dify 务须确…