链表 之 无头结点【哨兵位】单向非循环链表【单链表】增删改查 等方法

系列文章目录

🎈 🎈 我的CSDN主页:OTWOL的主页,欢迎!!!👋🏼👋🏼
🎉🎉我的C语言初阶合集:C语言初阶合集,希望能帮到你!!!😍 😍
🔍🔍我的C语言进阶合集:我的C语言进阶合集,期待你的点击!!!🌈🌈
🎉🎉我的数据结构与算法合集:数据结构与算法合集,点进去看看吧!!! 🎊🎊
😍 👋🏼🎉🎊创作不易,欢迎大家留言、点赞加收藏!!! 🥳😁😍

文章目录

  • 系列文章目录
  • 前言
  • 一、链表
    • (1)定义
    • (2)分类
  • 二、单链表(无头结点单向非循环链表)
    • (1)定义
    • (2)物理结构
  • 三、单链表的增删改查等方法的实现
    • (1)打印单链表
    • (2)动态申请新结点
    • (3)尾插单链表
    • (4)头插单链表
    • (5)尾删单链表
    • (6)头删单链表
    • (7)单链表查找
    • (8)在 pos 位置 之前插入新的结点
    • (9)删除在 pos 位置的结点
    • (10)在 pos 位置 之后插入新的结点
    • (11)删除 pos 位置之后的结点
    • (12)单链表的销毁
  • END


前言

各位博友,大家好!👋 今天为大家分享 单链表 的总结🔍。


一、链表

(1)定义

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

(2)分类

链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:

补充说明:

带头链表里的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这里“放哨的”

二、单链表(无头结点单向非循环链表)

(1)定义

无头结点单向非循环链表:结构简单,一般不会单独用来存数据。
实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
另外这种结构在笔试面试中出现很多。

(2)物理结构

补充说明:
结点的组成主要有两个部分:当前结点要保存的数据(data)和保存下⼀个结点的地址(指针变量next)。

三、单链表的增删改查等方法的实现

(1)打印单链表

// 定义函数 SLTPrint,用于打印单链表
void SLTPrint(SLTNode* phead) 
{
    // 定义一个指针 cur,用于遍历链表,初始指向链表头结点
    SLTNode* cur = phead;
    // 使用 while 循环遍历链表,直到 cur 指向 NULL,即链表结束
    while (cur) 
    {
        // 打印当前结点的数据域
        printf("%d->", cur->data);
        // 将 cur 指针移动到下一个结点
        cur = cur->next;
    }
    // 当遍历结束,打印"NULL"表示链表结束
    printf("NULL\n");
}

(2)动态申请新结点

// 定义函数 BuySLTNode,用于动态申请一个单链表结点,并初始化其数据
SLTNode* BuySLTNode(SLTDataType x) 
{
    // 初始化 newnode 为 NULL,用于存放新申请的结点
    SLTNode* newnode = NULL;
    // 动态申请一个 SLTNode 大小的内存空间,并将其首地址以 SLTNode 指针形式返回
    SLTNode* tmp = (SLTNode*)malloc(sizeof(SLTNode));
    // 如果内存申请失败,打印错误信息并返回 NULL
    if (tmp == NULL) 
    {
        perror("malloc fail");
        return NULL;
    }
    // 将 tmp 赋值给 newnode,表示 newnode 指向新申请的结点
    newnode = tmp;
    // 初始化新结点的数据域为传入的参数 x
    newnode->data = x;
    // 初始化新结点的 next 指针为 NULL,表示该结点目前没有后继结点
    newnode->next = NULL;

    // 返回新申请并初始化的结点
    return newnode;
}

(3)尾插单链表

// 定义函数 SLTPushBack,用于在单链表尾部插入一个新结点
void SLTPushBack(SLTNode** pphead, SLTDataType x) 
{
      // 断言 pphead 不为空,确保指向单链表首结点指针的指针 pphead 有效
    assert(pphead);

    // 动态申请一个新结点,并初始化其数据为 x
    SLTNode* newnode = BuySLTNode(x);

    // 如果链表为空(首结点为空),则将新节点设置为首结点
    if (*pphead == NULL) 
    {
        *pphead = newnode;
    }
    // 如果链表不为空
    else 
    {
        // 定义一个指针 tail,用于遍历链表找到尾结点
        SLTNode* tail = *pphead;
        // 遍历链表直到 tail 指向尾结点(tail->next 为 NULL)
        while (tail->next) 
        {
            tail = tail->next;
        }
        // 将尾结点的 next 指针指向新结点,完成尾部插入操作
        tail->next = newnode;
    }
}

(4)头插单链表

// 定义函数 SLTPushFront,用于在单链表头部插入一个新结点
void SLTPushFront(SLTNode** pphead, SLTDataType x) 
{
	 // 断言 pphead 不为空,确保指向单链表首结点指针的指针 pphead 有效
    assert(pphead);

    // 动态申请一个新结点,并初始化其数据为 x
    SLTNode* newnode = BuySLTNode(x);

    // 将新结点的 next 指针指向当前的首结点,这样新节点就成为了新的首结点
    newnode->next = *pphead;
    // 更新头指针,使其指向新结点
    *pphead = newnode;
}

(5)尾删单链表

// 定义函数 SLTPopBack,用于删除单链表尾部的结点
void SLTPopBack(SLTNode** pphead) 
{
    // 断言 pphead 不为空,确保指向单链表首结点指针的指针 pphead 有效
    assert(pphead);
    // 断言链表不为空,确保至少有一个结点
    assert(*pphead);

    // 如果链表只有一个结点,直接释放该结点并设置首指针为 NULL
    if ((*pphead)->next == NULL) 
    {
        free(*pphead);
        *pphead = NULL;
    }
    // 如果链表有多个结点
    else 
    {
        // 定义两个指针,tail 指向当前尾结点,prev 指向尾节点的前一个结点
        SLTNode* tail = *pphead;
        SLTNode* prev = NULL;
        // 遍历链表直到 tail 指向尾结点(tail->next 为 NULL)
        while (tail->next) 
        {
            prev = tail;
            tail = tail->next;
        }
        // 将 prev 的 next指针设置为 NULL,从链表中移除尾结点
        prev->next = NULL;
        // 释放尾结点的内存
        free(tail);
        // 将 tail 设置为 NULL,避免悬挂指针
        tail = NULL;
    }
}

(6)头删单链表

// 定义函数 SLTPopFront,用于删除单链表的首结点
void SLTPopFront(SLTNode** pphead) 
{
    // 断言 pphead 不为空,确保指向单链表首结点指针的指针 pphead 有效  
    assert(pphead);
    // 断言链表不为空,确保至少有一个结点
    assert(*pphead);

    // 保存当前首结点的指针,用于后续释放内存
    SLTNode* first = *pphead;
    // 将头指针更新为首结点的下一个结点,即新的首结点
    *pphead = first->next;
    // 释放原首结点的内存
    free(first);
    // 将 first 指针设置为 NULL,避免悬挂指针
    first = NULL;
}

(7)单链表查找

// 定义函数 SListFind,用于在单链表中查找一个特定值的结点
SLTNode* SListFind(SLTNode* plist, SLTDataType x) 
{
    // 初始化指针 cur 指向链表的首结点 plist
    SLTNode* cur = plist;
    // 使用 while 循环遍历链表,直到 cur 指向 NULL,即链表结束
    while (cur) 
    {
        // 如果当前结点的数据域 data 等于要查找的值 x
        if (cur->data == x) 
        {
            // 返回当前结点的指针,表示找到了匹配的结点
            return cur;
        }
        // 将 cur 指针移动到下一个结点
        cur = cur->next;
    }
    // 如果遍历完整个链表都没有找到匹配的结点,返回 NULL
    return NULL;
}

(8)在 pos 位置 之前插入新的结点

// 定义函数 SLTInsert,用于在单链表的 pos 位置之前插入一个新结点,该新结点的数据为 x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) 
{
      // 断言 pphead 不为空,确保指向单链表首结点指针的指针 pphead 有效
    assert(pphead);
    // 断言 pos 不为空,确保传入的位置结点有效
    assert(pos);

    // 动态申请一个新节点,并初始化其数据为 x
    SLTNode* newnode = BuySLTNode(x);

    // 如果 pos 是首结点,即在头部插入新结点
    if (pos == *pphead) 
    {
        SLTPushFront(pphead, x); // 调用头插函数插入新结点
    }
    // 如果 pos 不是首结点也不是空结点
    else 
    {
        // 定义一个指针 prev,用于遍历链表找到 pos 的前一个结点
        SLTNode* prev = *pphead;
        // 遍历链表直到 prev 的 next 指向 pos
        while (prev->next != pos) 
        {
            prev = prev->next;
        }
        // 将 prev 的 next 指向新结点,并将新结点的 next 指向 pos,完成插入操作
        prev->next = newnode;
        newnode->next = pos;
    }
}

(9)删除在 pos 位置的结点

// 定义函数 SLTErase,用于删除单链表中位于 pos 位置的结点
void SLTErase(SLTNode** pphead, SLTNode* pos) 
{
  // 断言 pphead 不为空,确保指向单链表首结点指针的指针 pphead 有效
    assert(pphead);
    // 断言 pos 不为空,确保传入的位置结点有效
    assert(pos);

    // 如果 pos 是首结点,调用头删函数删除首结点
    if (pos == *pphead) 
    {
        SLTPopFront(pphead);
    }
    // 如果 pos 不是首结点也不是空结点
    else 
    {
        // 定义一个指针 prev,用于遍历链表找到 pos 的前一个结点
        SLTNode* prev = *pphead;
        // 遍历链表直到 prev 的 next 指向pos
        while (prev->next != pos) 
        {
            prev = prev->next;
        }
        // 将 prev 的 next 指向 pos 的下一个节点,从而从链表中移除 pos 结点
        prev->next = pos->next;
        // 释放 pos 结点的内存
        free(pos);
    }
}

(10)在 pos 位置 之后插入新的结点

// 定义函数 SListInsertAfter,用于在单链表中 pos 位置的结点之后插入一个新的结点,新结点的数据为 x
void SListInsertAfter(SLTNode* pos, SLTDataType x) 
{
    // 动态申请一个新的结点,并初始化其数据为 x
    SLTNode* newnode = BuySLTNode(x);
    // 将新结点的 next 指针指向 pos 节点的下一个节点
    newnode->next = pos->next;
    // 将 pos 结点的 next 指针更新为新结点,完成在 pos 之后插入新结点的操作
    pos->next = newnode;
}

(11)删除 pos 位置之后的结点

// 定义函数 SListEraseAfter,用于删除单链表中 pos 位置结点之后的结点
void SListEraseAfter(SLTNode* pos) 
{
    // 断言 pos 不为空,确保传入的位置结点有效
    assert(pos);
    // 断言 pos 的下一个结点不为空,确保有结点可以删除
    assert(pos->next);

    // 保存 pos 结点的下一个结点的指针,这个结点将被删除
    SLTNode* del = pos->next;
    // 将 pos 结点的 next 指针更新为 del 结点的下一个结点,从而跳过 del 结点
    pos->next = del->next;
    // 释放 del 结点的内存
    free(del);
    // 将 del 指针设置为 NULL,避免悬挂指针
    del = NULL;
}

(12)单链表的销毁

// 定义函数 SLTDestroy,用于销毁单链表,释放所有结点的内存
void SLTDestroy(SLTNode** pphead) 
{
    // 断言 pphead 不为空,确保指向单链表首结点指针的指针 pphead 有效
    assert(pphead);
    // 初始化 cur 指针指向链表的头结点
    SLTNode* cur = *pphead;
    // 初始化 next 指针,用于临时存储 cur 结点的下一个结点
    SLTNode* next = NULL;
    // 使用 while 循环遍历链表,直到 cur 指向 NULL,即链表结束
    while (cur) 
    {
        // 保存当前节点的下一个结点到 next
        next = cur->next;
        // 释放当前结点的内存
        free(cur);
        // 将 cur 指针更新为 next,继续遍历
        cur = next;
    }
    // 遍历结束后,将首指针设置为 NULL,表示链表已被销毁
    *pphead = NULL;
}

END

每天都在学习的路上!
On The Way Of Learning

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GCP Cloud Architect exam - PASS

备考指南 推荐视频课程 https://www.udemy.com/course/google-cloud-architect-certifications/?couponCodeKEEPLEARNING 推荐题库 https://www.udemy.com/course/gcp-professional-cloud-architect-exam-practice-tests-2024​/?couponCodeKEEPLEARNING 错题集 http…

CCF-GESP 等级考试 2023年12月认证C++二级真题解析

2023年12月真题 一、单选题(每题2分,共30分) 正确答案:C 考察知识点:变量的定义与使用 解析:变量命名规则:1、只能包括数字、字母和下划线;2、不能以数字开头;3、不能和…

5.学习webpack配置 babel基本配置

babel是一个javascript编译工具,其主要功能是将新版本的JavaScript代码(如es6)转换为旧版本的代码(如es5),以便能够在旧版本的浏览器或环境中运行。一般配合webpack使用。 使用npm i -D babel/core babel/p…

配置搜索无人机

升级ubuntu内核 https://www.bilibili.com/video/BV11X4y1h7qN/?spm_id_from333.337.search-card.all.click 进入四个内核文件并安装 sudo dpkg -i *.deb安装ROS,PX4,XTDrone,QGC https://blog.csdn.net/qq_45493236/article/details/13…

Linux内核蓝牙子系统有什么(9)

接前一篇文章:Linux内核蓝牙子系统有什么(8) 本文内容参考: Linux之蓝牙相关代码浅析 | DDNotes 蓝牙驱动相关代码_蓝牙驱动代码-CSDN博客 linux蓝牙驱动代码阅读笔记_bt-sco.c-CSDN博客 Linux内核的蓝牙子系统架构-CSDN博客 …

22. 仿LISP运算

题目描述 LISP语言唯一的语法就是括号要配对 形如(OP P1 P2 ...),括号内元素由单个空格分割。其中第一个元素OP为操作符,后续元素均为其参数,参数个数取决于操作符类型。注意:参数P1,P2也有可能是另外一个嵌套的(OP P1 P2...),当前…

Axure10

如果还是不行就将字体图标安装在控制面板–字体下 打开原型了之后,icon没有 一定要将字体库放到–》控制面板\外观和个性化\字体 里面

王佩丰24节Excel学习笔记——第十九讲:Indirect函数

【以 Excel2010 系列学习,用 Office LTSC 专业增强版 2021 实践】 【本章技巧】 如果indirect引用出错,首先检查一下引用位置的双引号有没有出错,再检查引用值的位置是否出错,如果是双引号出错,可以使用英文状态下输入…

基于 Ragflow 搭建知识库-初步实践

基于 Ragflow 搭建知识库-初步实践 一、简介 Ragflow 是一个强大的工具,可用于构建知识库,实现高效的知识检索和查询功能。本文介绍如何利用 Ragflow 搭建知识库,包括环境准备、安装步骤、配置过程以及基本使用方法。 二、环境准备 硬件要…

Structured-Streaming初识

一、概览 Structured Streaming是一个基于SparkSQL引擎构建的可扩展且容错的流处理引擎。可以像在静态数据上表达批量计算一样表达流计算。SparkSQL引擎将负责以增量方式连续运行它,并在流数据继续到达时更新最终结果。可以使用Scala、Java、Python或R中的Dataset/…

Gradio全解系列——Additional Features:附加功能(上)

Gradio全解系列——Additional Features:附加功能(上) 前言本篇摘要10. Additional Features:附加功能10.1 队列10.1.1 使用方法10.1.2 配置队列 10.2 流输入输出10.2.1 流输出1. 生成器yield2. 流媒体 10.2.2 流输入1. 流事件2. …

TestMAX/DFT Compiler:时序单元的类型、连接顺序和后DFT优化

相关阅读 TestMAX/DFT Compilerhttps://blog.csdn.net/weixin_45791458/category_12865937.html?spm1001.2014.3001.5482 时序单元的状态 未映射的时序单元(Unmapped Sequential Cell) 在Design Compiler读取了一个RTL设计后,Design Compiler内置的HDL Compiler工…

Cocos Creator 3.8.5 正式发布,更小更快更多平台!

在 Cocos Creator 3.8.5 版本中,我们做了新一轮的优化。 在加载速度、代码裁剪、平台增强等多方面做了优化,提升了开发者体验和游戏性能。 希望能够助 Cocos 开发者们的产品更上一层楼。 一、加载速度优化 1、WASM 模块延迟加载 在早期版本中&#xff0c…

跨语言数据格式标准化在 HarmonyOS 开发中的实践

文章目录 前言数据格式标准化的意义数据传递中的痛点标准化的优势 JSON 与 Protocol Buffers 的比较JSONProtocol Buffers HarmonyOS 跨语言数据传递示例示例代码:定义 Protocol Buffers 消息格式生成 Java 和 C 代码示例代码:Java 端序列化与传递数据C …

【有作图代码】多尺度动力学模型:像“显微镜与望远镜的结合”,揭示微观分子运动与宏观流体流动的奥秘

【有作图代码】多尺度动力学模型:像“显微镜与望远镜的结合”,揭示微观分子运动与宏观流体流动的奥秘 具体实例与推演 假设我们有一个流体系统,其中微观尺度上分子间的相互作用可以通过分子动力学方程描述,而宏观尺度上流体的流…

工具变量笔记

补充知识 简单介绍工具变量 假设 Y i α β D i ϵ i Y_i\alpha\beta D_i\epsilon_i Yi​αβDi​ϵi​, where E ( ϵ i ∣ D i ) 0 E(\epsilon_i\mid D_i)0 E(ϵi​∣Di​)0. 但是通常这个条件不满足。于是假如有这样一个工具变量 Z i Z_i Zi​存在的话,满…

通过 Ansys Electronics Desktop 中的高级仿真优化 IC 设计

半导体行业继续通过日益复杂的集成电路 (IC) 设计突破技术界限。随着工艺节点缩小和电路密度达到前所未有的水平,电磁效应对设备性能和可靠性变得越来越重要。现代 IC 设计面临着来自复杂的布局相关耦合机制、信号完整性问题和功率分布问题的挑战,这些问…

Yocto 项目中的交叉编译:原理与实例

Yocto 项目是一个强大的工具集,它专注于为嵌入式系统生成定制的 Linux 发行版。交叉编译在 Yocto 项目中扮演着核心角色,它使得开发者能够在功能强大的宿主机上构建适用于资源受限目标设备的软件系统。这篇文章将从运行原理、实际案例和工具链组成等角度…

WPF 绘制过顶点的圆滑曲线(样条,贝塞尔)

项目中要用到样条曲线,必须过顶点,圆滑后还不能太走样,捣鼓一番,发现里面颇有玄机,于是把我多方抄来改造的方法发出来,方便新手: 如上图,看代码吧: -------------------…

谷粒商城-高级篇-秒杀业务

1、后台添加秒杀商品 1、配置网关 - id: coupon_routeuri: lb://gulimall-couponpredicates:- Path/api/coupon/**filters:- RewritePath/api/(?<segment>.*),/$\{segment} 2、每日秒杀关联商品功能实现 点击关联商品后&#xff0c;应该查询当前场次的所有商品 点击关…