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

数据结构_单链表

在这里插入图片描述

今天我们要开启链表的学习

🖋️🖋️🖋️
学了顺序表我们可以知道:
🎈链表其实就是争对顺序表的缺点来设计的,补足的就是顺序表的缺点
🎈链表在物理上是上一个节点存放的下一个节点的地址

链表

1.链表的概念及结构

1.1概念

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

1.2结构
在这里插入图片描述
根据上图我们可以知道:
链表是由一系列节点组成的线性数据结构,每个节点包含数据部分和指向下一个节点的指针(在双向链表中还有指向前一个节点的指针)。节点通过指针连接在一起,形成一个链式结构,数据在内存中存储并不连续。

2.链表的分类

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1.单向、双向
2.带头、不带头
3.循环、非循环
(这里图就不展示啦,大家感兴趣的可以自己去查找噢)

3.链表的实现

1.创建一个节点

typedef int SListDataType;
//节点
typedef struct SListNode
{//定义一个数据SListDataType data;//这个data就是下图中的1 2 3//定义一个指针struct SListNode* next;//next就是存放的指针
}SListNode;

这部分代码定义了一个单链表的节点结构:

  • struct SListNode:定义了一个名为 SListNode 的结构体,用于表示单链表中的一个节点。
  • SListDataType data;:在结构体中定义了一个成员变量 data,其数据类型为之前定义的 SListDataType(这里实际上就是 int 类型)。这个成员变量用于存储链表节点所包含的数据。
  • struct SListNode* next;:定义了一个指向struct SListNode类型的指针 next。这个指针用于指向下一个链表节点,通过它可以将多个节点连接起来形成链表。

2.遍历打印链表

void SListPrint(SListNode* phead)
{//这里不需要assert断言,因为这里为空的话就是空链表SListNode* cur = phead;//指向第一个节点while (cur != NULL){printf("%d ", cur->data);cur = cur->next; }
}

画图解释:
在这里插入图片描述

3.尾插

//动态申请新节点并初始化
SListNode* BuySListNode(SListDataType x)
{SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));//在内存的堆区动态分配一块连续的内存空间,大小为 SListNode 结构体的字节数,再强转为SListNode*型if (newNode == NULL){printf("申请结点失败\n");exit(-1);}newNode->data = x;//将传入的数据 x 存储到新节点的 data 成员newNode->next = NULL;//初始化新节点的 next 指针为 NULL(新节点暂时无后续节点)return newNode;//返回创建好的节点地址
}//尾插
void SListPushBack(SListNode** pphead, SListDataType x)
{SListNode* newNode = BuySListNode(x);//调用函数创建待插入的新节点if (*pphead == NULL){*pphead = newNode;//若链表为空,直接让头指针指向新节点}else{//找尾SListNode* tail = *pphead;//定义指针 tail 指向链表头部while (tail->next != NULL)//遍历链表,找到尾节点(尾节点的 next 为 NULL){tail = tail->next;//实现遍历链表的关键代码,将指针 tail 移动到下一个节点}tail->next = newNode;//将尾节点的 next 指针指向新节点,完成尾插}}

问题:动态申请return newNode;为什么要返回创建好的节点地址

返回新节点的地址是为了让调用者能够:

  1. 节点插入链表中。
  2. 后续操作节点的数据或指针。
  3. 正确释放内存(避免泄漏)。

这是 C 语言中动态内存管理的标准做法,确保函数间的数据传递和内存控制权的转移。

4.尾删

//尾删
void SListPopBack(SListNode** pphead)
{//1.空//2.一个结点//3.一个以上结点// 处理空链表if (*pphead == NULL){return;//直接返回,没得删}// 处理单节点链表else if ((*pphead)->next ==NULL){free(*pphead);*pphead = NULL;//只有这一个就直接删}// 处理多节点链表else{SListNode* prev = NULL;//用于记录尾节点的前驱节点SListNode* tail = *pphead;//用于遍历链表while (tail->next  != NULL){prev = tail;//在循环中,prev 始终指向 tail 的前一个节点,tail 逐步后移tail = tail->next;//再往后走}free(tail);tail = NULL;prev->next = NULL;//将其存放的地址置空,避免内存泄漏和悬空指针}
}

5.头插

//头插
void SListPushFront(SListNode** pphead, SListDataType x)
{SListNode* newnode = BuySListNode(x);//创建新节点newnode->next = *pphead;//新节点的 next 指向原头节点*pphead = newnode;//更新头指针为新节点
}

步骤分析:

  • 步骤 1:创建新节点
    调用 BuySListNode(x)
    动态分配内存,初始化data xnext NULL
  • 步骤 2:连接新节点与原头节点
    newnode->next = *pphead;
    若原链表非空,新节点的next指向原头节点(形成链式关系)。
    若原链表为(*pphead == NULL),新节点的next仍为 NULL
  • 步骤 3:更新头指针
    *pphead = newnode;
    将头指针pphead指向新节点,使其成为链表的新头部。

画图解释:
在这里插入图片描述
注意:

  1. 核心原因:头指针是链表的入口,必须指向第一个节点。头插法中,新节点成为第一个节点,因此必须更新头指针。
  2. 技术实现:通过双重指针修改原始头指针,确保链表结构正确。若省略此步骤,新节点将无法被链表访问,导致内存泄漏或逻辑错误。

6.头删

//头删
void SListPopFront(SListNode** pphead)
{//1.空//2.一个节点 + 3.一个以上节点if (*pphead == NULL){return;}else{SListNode* next = (*pphead)->next;//保存头节点的下一个节点 free(*pphead);// 释放头节点内存*pphead = next;// 更新头指针为原头节点的下一个节点  }
}

详细分析:

  • 保存后续节点:SListNode* next = (*pphead)->next; 记录原头节点下一个节点,防止链表断裂。
  • 释放内存:free(*pphead); 释放原头节点的内存,避免内存泄漏。
  • 更新头指针:*pphead = next; 使头指针指向原头节点的下一个节点,完成头删。

举个例子:
在这里插入图片描述
注意

  1. 左边 next:是程序员自定义的指针变量,用于临时存储地址。
  2. 右边 next:是链表节点结构体固有的成员,用于维系链表的链式结构。

二者仅名称相同,但一个是变量标识,一个是结构体成员,功能和性质完全不同。

7.单链表查找

// 单链表查找
SListNode* SListFind(SListNode* phead, SListDataType x)
{SListNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}

详细分析:

  • SListNode* cur = phead;定义一个指针cur并初始化为 phead,用于遍历单链表。cur 会从链表的头节点开始,依次向后移动;
  • 在循环内部,使用if语句检查当前节点cur的数据域data是否等于要查找的值 x。如果相等,则表示找到了目标节点,直接返回 cur,即该节点的指针
  • cur = cur->next;如果当前节点的数据域不等于 x,则将cur指针更新为指向下一个节点,继续向后遍历链表。
  • return NULL;如果while循环结束后仍未找到值为x的节点,说明链表中不存在该值,此时返回 NULL

在test函数中实现时:

在这里插入图片描述

最后的打印结果为:
(将3修改为了30)
在这里插入图片描述

8.单链表在pos位置之插入x

void SListInsertAfter(SListNode* pos, SListDataType x)
{assert(pos);SListNode* newnode = BuySListNode(x);newnode->next = pos->next;pos->next = newnode;
}

问题:分析思考为什么不在pos位置之前插入?

原因:单链表的单向性决定了插入操作必须依赖前驱节点的遍历,若pos无法通过遍历到达(如越界或无前驱),则无法实现插入。

实例:若链表为1→2→3,想在节点2前插入0,需先找到节点1(前驱),再修改其指针为1→0→2→3。若无法找到前驱(如pos为4),则插入失败。

关键指针操作分析

  • newnode->next = pos->next;
    pos->next pos节点原本指向的下一个节点的指针。
    这行代码的作用是让新节点newnodenext指针指向pos节点原来的下一个节点。其目的是在插入新节点时,保证新节点能够连接到原链表中pos节点之后的部分,避免丢失后续的节点。可以把它理解为 “记住”pos节点后面的节点,以便后续正确连接链表。
  • pos->next = newnode;
    这行代码将pos节点的next指针指向新节点 newnode
    结合上一步,这就完成了将新节点插入到 pos 节点之后的操作。此时,pos 节点后面跟着新节点 newnode,而新节点 newnode 后面接着原链表中 pos 节点原本的后续节点,链表结构更新成功。

画图分析:
在这里插入图片描述

9.单链表删除pos位置之的值

// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{assert(pos);if (pos->next)//不为空{SListNode* next = pos->next;SListNode* nextnext =next->next;pos->next = nextnext;free(next);}
}

分析思考为什么不删除pos位置?

原因:在单链表中,直接删除pos位置节点需要遍历链表来寻找前驱节点,这会增加代码的复杂度和时间成本。因此,通常建议优先采用删除后继节点或者复制后继节点值的替代方案。如果确实需要删除pos节点,就必须传递头指针的地址(即二级指针),以便在头节点被删除时能够正确更新链表的头指针。

画图分析:
在这里插入图片描述
详细分析:
🖋️🖋️🖋️
pos->next也就是方框1的后半个位置,存放的是next的地址;通过next指向的next也就是第二个next的后半个方框,存放的是第三个nextnext的地址;所以删除后直接将nextnext所存放的地址赋值给pos->next!

如何去调用呢?

在这里插入图片描述

10.接口实现

在这里插入图片描述
🎉🎉🎉
到这里本章就结束啦~
我们下节见~

相关文章:

数据结构_单链表

今天我们要开启链表的学习 🖋️🖋️🖋️ 学了顺序表我们可以知道: 🎈链表其实就是争对顺序表的缺点来设计的,补足的就是顺序表的缺点 🎈链表在物理上是上一个节点存放的下一个节点的地址 链表 …...

深陷帕金森困境,怎样重燃生活信心?

帕金森,这个悄然影响无数中老年人生活的神经系统疾病,正逐渐走进大众视野。患病后,患者常出现静止性震颤,安静时手部、下肢不自主抖动,如同在默默诉说着身体的异常。肢体变得僵硬,行动迟缓,起步…...

C语言(23)

字符串函数 11.strstr函数 1.1函数介绍: 头文件:string.h char *strstr ( const char * str1,const char *str2); 作用:在一个字符串(str1)中寻找另外一个字符串(str2)是否出现过 如果找到…...

Docker运行hello-world镜像失败或超时:Unable to find image ‘hello-world:latest‘ locally Trying to pull reposi

Docker运行hello-world镜像失败或超时,报错:Unable to find image ‘hello-world:latest’ locally Trying to pull repository docker.io/library/hello-world … /usr/bin/docker-current: missing signature key. See ‘/usr/bin/docker-current run …...

Linux内核如何和设备树协同工作的?

1.编写设备树 cd arch/riscv/boot/dts/ 再cd到厂商,例如下述内容。 2.编译设备树(dts->dtb)通过dtc命令来转换 3.解析设备树 例如上述内容,都是对设备树的解析。 这里重点说一下内核对设备树的处理吧,因为这个内…...

electron的通信方式(三种)

文章目录 一、渲染进程向主进程发送消息二、渲染进程向主进程发送消息并异步获取结果三、主进程向渲染进程发送消息 electron的主要是主线程和渲染线程之间的通信,简单记录一下三种通信方式 一、渲染进程向主进程发送消息 利用ipcRenderer.send()和ipcMain.on()方法…...

LLM中的transformer结构学习(二 完结 Multi-Head Attention、Encoder、Decoder)

文章目录 LLM中的transformer结构学习(二 完结 Multi-Head Attention、Encoder、Decoder)Self-Attention (自注意力机制)结构多头注意力 EncoderAdd & Norm 层Feed Forward 层 EncoderDecoder的第一个Multi-Head AttentionMas…...

高效编程指南:PyCharm与DeepSeek的完美结合

DeepSeek接入Pycharm 前几天DeepSeek的充值窗口又悄悄的开放了,这也就意味着我们又可以丝滑的使用DeepSeek的API进行各种辅助性工作了。本文我们来聊聊如何在代码编辑器中使用DeepSeek自动生成代码。 注:本文适用于所有的JetBrains开发工具&#xff0c…...

嵌入式c学习四

c语言的输入输出:ANSI组织发布的标准c库,使用函数需要包含对应头文件,使用输入输出函数时需要包含stdio.h (stdio -> standard input output)标准输入输出 格式化输出:int printf(const char * restrict format, ...)&#xf…...

爱可以传递,幸福可以具象化

遇到什么:晚上上课学生吵吵吵,把学生手机全部收了,放讲台上。 感受到的情绪:很烦躁。 反思:收手机也不是长久之计,可是物理有什么翻转课堂呢? 明天的待办事项:早上高数选修课&#x…...

力扣-数组-367 有效的完全平方数

思路和时间复杂度 思路:利用二分,确定区间是左闭右闭,然后根据大小进行二分时间复杂度: 代码 class Solution { public:bool isPerfectSquare(int num) {bool flag false;if(num 0 || num 1) return true;long long …...

Java关键字与标识符

Java关键字是预定义的保留字,用于定义程序结构和语义,如if、for、class等,不能用作标识符。JDK 8有50个关键字,JDK 11引入var用于局部变量类型推断。标识符用于命名变量、类等,由字母、数字、_、$组成,不能…...

【神经网络】python实现神经网络(二)——正向推理的模拟演练

一.神经网络假设 在开始讲解之前,首先我们假设有这样一套神经网络,一共有三层: 其中,关于神经网络的权重、偏置的符号定义如下(如果不知道什么是权重和偏置,可以参考我之前写过的一篇文章:【机器学习】机器学习是什么意思): 以下文章将沿用以上这个设…...

DeepSeek模型本地化部署方案及Python实现

DeepSeek实在是太火了,虽然经过扩容和调整,但反应依旧不稳定,甚至小圆圈转半天最后却提示“服务器繁忙,请稍后再试。” 故此,本文通过讲解在本地部署 DeepSeek并配合python代码实现,让你零成本搭建自己的AI…...

Rust规律归纳随笔

1. 针对所有权规则&#xff1a; 唯一所有权 <------------> 智能指针(引用计数)<-------------->共享所有权 (引用计数) { 单线程&#xff1a;Rc 多线程&#xff1a;Arc } 2. 针对借用规则&#xff1a; 共享不可变&#xff08;多读&#xff09;<----->…...

Houdini SOP层 Scatter节点

SOP 代表 Surface Operator&#xff08;几何体操作节点&#xff09;&#xff0c;所有几何体的建模、变形、分布等操作都在此层级完成。 Scatter节点的作用就是 以不同的密度在模型表面撒点 Scatter 节点属于 SOP&#xff08;几何体&#xff09;层级&#xff1a; 进入 Geometr…...

Win7 火狐浏览器 Mozilla Firefox 115.7.0esr下载地址(及Chrome、Supermium浏览器)

如题&#xff0c;官网下载地址&#xff1a; Mozilla Firefox 115.7.0esr 已经发布&#xff0c;感兴趣的朋友可去官方下载&#xff01; https://ftp.mozilla.org/pub/firefox/releases/115.7.0esr/ 其他方案&#xff1a; 1、谷歌 Chrome 浏览器的 109版本&#xff0c;即最后一…...

Docker 学习笔记:从入门到部署,实战演练全流程!

&#x1f4cc; 开篇&#xff1a;为什么要学 Docker&#xff1f; 还在为环境不一致、部署麻烦、依赖冲突头疼吗&#xff1f;Docker 让一切变得简单&#xff01;作为现代开发和运维的神器&#xff0c;Docker 让我们可以用 一句命令 解决 “在我电脑上能跑” 的问题。今天&#x…...

【网络安全工程】任务12:网络安全设备

目录 一、防火墙​ 1、作用​ 2、配置方式​ 3、存在的漏洞​ 二、入侵检测系统&#xff08;IDS&#xff09;和入侵防御系统&#xff08;IPS&#xff09;​ 1、作用​ 2、配置方式​ 3、存在的漏洞​ 三、防病毒网关​ ​1、作用​ 2、配置方式​ 3、存在的漏洞​ …...

Linux 进程控制:创建、终止、等待与程序替换全解析

亲爱的读者朋友们&#x1f603;&#xff0c;此文开启知识盛宴与思想碰撞&#x1f389;。 快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 目录 1.进程创建 1-1 fork函数初识​ 1-2 fork函数返回值​ 1-3…...

SwiftUI 让视图自适应高度的 6 种方法(四)

概览 在 SwiftUI 的世界里&#xff0c;我们无数次都梦想着视图可以自动根据布局上下文“因势而变”‌。大多数情况下&#xff0c;SwiftUI 会将每个视图尺寸处理的井井有条&#xff0c;不过在某些时候我们还是得亲力亲为。 如上图所示&#xff0c;无论顶部 TabView 容器里子视图…...

机器学习中的梯度下降是什么意思?

梯度下降&#xff08;Gradient Descent&#xff09;是机器学习中一种常用的优化算法&#xff0c;用于最小化损失函数&#xff08;Loss Function&#xff09;。通过迭代调整模型参数&#xff0c;梯度下降帮助模型逐步逼近最优解&#xff0c;从而提升模型的性能。 1.核心思想 梯…...

opencv-显示图片

安装软件 sudo apt install python3 //确保虚拟机只有python3 ln -sf /usr/bin/python3.6 /usr/bin/python sudo apt install python3-pip pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple pip install opencv-contrib-python -i https://pypi.tuna…...

sap关账+策略模式(避免大量if elseif)

旧代码 Transactional(rollbackFor Exception.class)public AjaxResult purchaseOrderReceiptOutSourceAfterSapCloseAccountingPeriod(Long id) {SysPurorderPostingLog sysPurorderPostingLog sysPurorderPostingLogMapper.selectSysPurorderPostingLogById(id);if (Object…...

EverArt MCP 服务器安装调试笔记 -cline

EverArt MCP 服务器安装调试笔记 问题描述 用户在使用 EverArt MCP 服务器时遇到报错&#xff1a;“MCP error -1: Connection closed”。 调试过程 检查配置文件 cline_mcp_settings.json: 确认 everart 服务器的配置信息&#xff0c;包括 command、args 和 env 是否正确。…...

1035.不相交的线

1035.不相交的线 力扣题目链接(opens new window) 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足&#xff1a; nums1[i] nums2[j]且绘制的直线…...

Django-ORM-select_related

Django-ORM-select_related 作用使用场景示例无 select_related 的查询有 select_related 的查询 如何理解 "只发起一次查询&#xff0c;包含所有相关作者信息"1. select_related 的工作原理2. 具体示例解析3. 为什么只发起一次查询 数据库中的books量巨大&#xff0…...

2001-2023年上市公司数字化转型年报词频统计(年报词频统计和MDA词频统计两种方式)(吴非、赵宸宇、甄红线300+关键词三种方法)

2001-2023年上市公司数字化转型年报词频统计&#xff08;年报词频统计和MD&A词频统计两种方式&#xff09;&#xff08;吴非、赵宸宇、甄红线300关键词三种方法&#xff09; 1、时间&#xff1a;2001-2023年 2、来源&#xff1a;上市公司年报 3、参考文献&#xff1a; …...

IO多路复用实现并发服务器

一.select函数 select 的调用注意事项 在使用 select 函数时&#xff0c;需要注意以下几个关键点&#xff1a; 1. 参数的修改与拷贝 readfds 等参数是结果参数 &#xff1a; select 函数会直接修改传入的 fd_set&#xff08;如 readfds、writefds 和 exceptfds&#xf…...

React 如何实现组件懒加载以及懒加载的底层机制

前言 在现代前端开发中&#xff0c;性能优化始终是一个核心课题。React 作为当下流行的前端库之一&#xff0c;提供了一些非常有用的工具和技术来提升应用的性能&#xff0c;其中懒加载&#xff08;Lazy Loading&#xff09;就是一项不可忽视的重要技术。通过懒加载&#xff0…...