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

【数据结构】单链表:数据结构中的舞者,穿梭于理论与实践的舞池

 欢迎来到白刘的领域   Miracle_86.-CSDN博客

         系列专栏   数据结构与算法

先赞后看,已成习惯

   创作不易,多多支持!

一、链表的概念和结构

1.1 链表的概念

在上一篇文章中,我们了解了线性表(linear list),并且学习了其中一种线性表——顺序表(Sequence List)链表也是线性表的一种,那么它是一种什么样的结构呢?

链表,顾名思义,带着链子的表。日常生活中,我们知道链子是用来链接两个东西的,那么我们可以很容易理解,顺序表是基于数组实现的,是一个元素一个元素挨着的,那链表我们就可以理解为每个元素用链子连接起来,这样就形成了链表(Linked List)。

1.2 链表的结构

那么我们得到了两个链表的组成的关键元素,一个是每个元素,我们管它叫节点(或结点),另一个就是那个链子。而在C语言中,我们用结构体来实现节点,用指针来充当链,连接两个节点。

在生活中链表类似于我们的火车的结构,在物理上它是一种存储结构非顺序非连续的结构。

节点的组成主要由两个部分组成:一个是数值域,用来保存当前节点的数据,一个是指针域,用来保存下一个节点的地址(指针变量)。

如图所示,指针变量plist保存的是第一个节点的地址,如果我们想让plist指向第二个节点,我们只需要将plist保存的内容修改为0x0012FFD0。

为什么我们需要指针变量来保存下一个节点的位置?

因为之前我们说了,链表不同于顺序表,它在内存中的地址不一定是连续的,它是独立申请的(对其插入数据时才申请一个节点)。我们需要通过指针才能从当前节点找到下一个节点。

所以我们可以通过一个结构体来实现一个节点,它要有一个节点的数据以及一个指针变量来保存下一个节点的地址,代码如下:

struct SListNode
{int data; //节点数据struct SListNode* next; //指针变量⽤保存下⼀个节点的地址
};

当然我们也可以用typedef来进行修改,方便我们后续操作。

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data; //节点数据struct SListNode* next; //指针保存下⼀个节点的地址
}SLTNode;

我们如何将链表进行打印呢?

首先我们将节点传到打印函数,然后我们创建了一个指向头结点的结构体指针,利用循环从头遍历到尾,每次打印完成令pcur指向pcur的next节点。这里注意的就是结构体成员的访问的问题,我们用到了->操作符,在前面的博客我们都介绍过。

武器大师——操作符详解(下)-CSDN博客

代码如下:

void SLTPrint(SLTNode* phead){SLTNode *cur = phead;while(cur){printf("%d ",cur->data);cur = cur->next;}printf("\n");
}

 二、单链表的实现

上面我们知道了什么是链表,那单链表又是什么呢?单链表(Singly Linked List),一般所指的是“不带头单向不循环链表”。

我们实现的功能无非就四个“增、删、查、改”。

2.1 增

2.1.1 尾插

尾插,顾名思义,在尾部插入,俗称后入(bushi)。

首先我们要申请新的节点,我们可以写一个申请节点的函数。

SLTNode* BuyNode(SLTDataType x) {SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));if (node == NULL) {perror("malloc fail!\n");exit(1);}node->data = x;node->next = NULL;return node;
}

首先我们用malloc函数动态申请一块内存,然后将节点的数据赋进去。

void SLTPushBack(SLTNode** pphead, SLTDataType x) {assert(pphead);SLTNode* node = BuyNode(x);if (*pphead == NULL) {*pphead = node;return;}//找尾SLTNode* cur = *pphead;while (cur->next) {cur = cur->next;}cur->next = node;
}

接下来的代码逻辑特别简单,首先我们通过buynode函数创建了一个节点,然后我们判空,如果这个链表是空的,我们直接将指针变量pphead赋为node。如果不是空的,那我们就需要找到链表的尾部,我们可以通过循环来找到尾部,首先为了防止原来数据被破坏,我们创建一个指向第一个节点的指针变量cur,而不是直接遍历pphead。

注意我们如何找尾,通过判断该节点的下一个位置是否为空。

这里还有一个细节就是,我们传入的是二级指针,因为我们要修改一个数据的话,需要传地址,而那个数据如果是指针的话,我们就需要传指针的地址,也就是二级指针。

2.1.2 头插
void SLTPushFront(SLTNode** pphead, SLTDataType x) {assert(pphead);SLTNode* node = BuyNode(x);node->next = *pphead;*pphead = node;
}

这个代码逻辑也很简单,首先我们assert断言防止访问空指针,之后buynode函数申请节点,然后将结点接到头部,也就是*pphead,之后别忘了将*pphead再指向node,形成新的头。

2.2 删

2.2.1 尾删

尾删也很简单,仔细思考,我们只需要两步就能完成,一个是找尾,一个是删除。

首先我们来看找尾,我们可以用循环,再加上两个指针,

   //找尾
//    SLTNode *cur = *pphead;
//    SLTNode *prev = NULL;
//    while(cur->next){
//        prev = cur;
//        cur = cur->next;
//    }
//    prev->next = NULL;
//    free(cur);
//    cur = NULL;

首先cur指向头,prev指向空,cur用来找尾,prev用来保存上一个节点的地址。当cur的下一个位置为NULL时循环结束,意味着找到最后一个节点了,我们将它所指的位置free掉(因为是动态开辟出来的),然后指针置空即可完成删除

还有一种方法可以用一个指针就解决,

  SLTNode* tail = *pphead;while (tail->next->next) {tail = tail->next;}free(tail->next);tail->next = NULL;

 我们判断条件改成这样,实际上我们找的tail是倒数第二个节点,所以最后的删除需要改成tail的next为NULL。

2.2.2 头删

头删的逻辑就更加简单了,我们只需要创建一个变量来保存原来的头节点,方便我们后续删除,然后让原头节点移到下一个结点成为新的头结点,然后删掉原来的。如果我们不保存,我们没有办法找到原来的头结点。

代码如下:

void SLTPopFront(SLTNode** pphead) {assert(pphead);assert(*pphead);SLTNode* first = *pphead;*pphead = (*pphead)->next;free(first);first = NULL;
}

2.3 查

这个实现也很简单,就是遍历,然后匹配。由于是查找,而不是对数据进行修改,所以传一级指针即可。

SLTNode* SLTFind(SLTNode* phead, SLTDataType x) {assert(phead);SLTNode* cur = phead;while (cur) {if (cur->data == x) {return cur;}cur = cur->next;}return NULL;
}

2.4 改

2.4.1 在pos前插入

将图画出来我们就清晰明了了。我们如果想把node插入到pos前,我们只需要将node的next指向pos,然后将prev的next指向node,这样就构成了插入操作。

试想一下,1和2的操作可以调换顺序吗?答案是不可以,这是因为如果我们先做2,这个时候我们就找不到pos了,也就不能执行1操作了。有人会问,我创建两个变量来记录这两个点的位置不可以吗?可以,但是没必要。

之后我们来看代码部分,首先我们要创建一个node节点,用buynode函数。正常情况,我们需要找pos的前一个节点prev,利用循环即可。如果只有一个节点怎么办呢?这个时候直接头插就可以了。

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {assert(pphead);assert(pos);SLTNode* node = BuyNode(x);if (*pphead == pos) {//        node->next = pos;//        *pphead = node;SLTPushFront(pphead, x);return;}//找pos的前一个节点SLTNode* prev = *pphead;while (prev->next) {if (prev->next == pos) {break;}prev = prev->next;}node->next = pos;prev->next = node;
}
2.4.2 删除pos

其实逻辑也是很简单的,要删除pos的话,我们需要找到pos前面的节点保存,否则就找不到了。

void SLTErase(SLTNode** pphead, SLTNode* pos) {assert(pphead);assert(pos);if (*pphead == pos) {//        SLTNode *del = *pphead;//        *pphead = (*pphead)->next;//        free(del);//        del = NULL;SLTPopFront(pphead);return;}//找pos的前一个节点SLTNode* prev = *pphead;while (prev->next) {if (prev->next == pos) {break;}prev = prev->next;}prev->next = pos->next;free(pos);//    pos = NULL;//没有存在的必要
}

并且要注意连接好pos后面的节点后再删除,不然也找不到。

2.4.3 在pos后插入

逻辑跟在pos前插入一样,也要注意顺序,只不过在pos之后插入不需要传链表第一个节点了,只需要传pos即可。

void SLTInsertAfter(SLTNode* pos, SLTDataType x) {assert(pos);SLTNode* node = BuyNode(x);node->next = pos->next;pos->next = node;
}
2.4.4 删除pos后的节点
void SLTEraseAfter(SLTNode* pos){assert(pos);assert(pos->next);SLTNode *del = pos->next;pos->next = del->next;free(del);del = NULL;
}

其实看到这你就发现,对链表的操作无非就是保存节点,然后连接,同时画图操作也可以对我们学习数据结构有所帮助。

2.5 链表的销毁

我们将链表每个节点通过循环遍历free掉即可,唯一比较吃操作的就是创建一个用于保存下一个节点的位置的指针。

//销毁链表
void SListDesTroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

相关文章:

【数据结构】单链表:数据结构中的舞者,穿梭于理论与实践的舞池

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 数据结构与算法 先赞后看,已成习惯 创作不易,多多支持! 一、链表的概念和结构 1.1 链表的概念 在上一篇文章中,我们了解了线性表(linear list),并且学习了其…...

html三级菜单

示例 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Menu Example</title> <link re…...

【人工智能】—基于成都市各区(市)县租房价格预测建模研究

引言 随着城市化进程的加速&#xff0c;人口流动日益频繁&#xff0c;租房市场作为城市生活的重要组成部分&#xff0c;其价格波动对居民生活质量和城市经济发展具有显著影响。成都市&#xff0c;作为中国西部地区的经济、文化、交通和科技中心&#xff0c;近年来吸引了大量人…...

3213. 最小代价构造字符串

Powered by:NEFU AB-IN Link 文章目录 3213. 最小代价构造字符串题意思路代码 3213. 最小代价构造字符串 题意 给你一个字符串 target、一个字符串数组 words 以及一个整数数组 costs&#xff0c;这两个数组长度相同。 设想一个空字符串 s。 你可以执行以下操作任意次数&a…...

提取重复数据

直接上控制台代码&#xff1a; Module Module1Sub Main()Console.WriteLine("请输入数据&#xff0c;以""&#xff0c;""相隔&#xff1a;")Dim str As String Console.ReadLineDim result From x In str.Split(",")Group By x Int…...

Go语言标准库之log和三方库zap

一、Log 1.1 logger基本使用 Go语言内置的log包实现了简单的日志服务。本包也提供了一个预定义的“标准”logger&#xff0c;可以通过调用函数Print系列(Print|Printf|Println)、Fatal系列&#xff08;Fatal|Fatalf|Fatalln)、和Panic系列&#xff08;Panic|Panicf|Panicln)来…...

Linux:进程终止和进程替换

Linux&#xff1a;Linux&#xff1a;进程终止和进程替换 一、进程终止1.1 进程退出场景和创建退出方式 1.2 exit 和 _exit区别二、进程程序替换2.1 进程替换函数2.2 函数解释及命名解释函数解释命名解释 2.3 单进程程序替换&#xff08;无子进程&#xff09;2.3.1 带l函数进程替…...

使用Java实现异步消息处理与队列消费

使用Java实现异步消息处理与队列消费 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 在现代软件系统中&#xff0c;处理异步消息和队列消费是常见的需求。通过…...

使用C++实现ATM系统,谈谈思路及代码实现

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…...

相机光学(二十四)——CRA角度

CRA角度 0.参考资料1.什么是CRA角度2.为什么 CRA 会导致luma shading3.为什么 CRA 会导致color shading4.CRA相差过大的具体表现5.CRA Matching6.怎样选择sensor的CRA 0.参考资料 1.芯片CRA角度与镜头的匹配关系&#xff08;一&#xff09;   2.芯片CRA角度与镜头选型的匹配关…...

python函数和c的区别有哪些

Python有很多内置函数&#xff08;build in function&#xff09;&#xff0c;不需要写头文件&#xff0c;Python还有很多强大的模块&#xff0c;需要时导入便可。C语言在这一点上远不及Python&#xff0c;大多时候都需要自己手动实现。 C语言中的函数&#xff0c;有着严格的顺…...

速看!这主食冻干评测极可能被商家恶意举报~PR、希喂和SC真实测评

我是一名专注于宠物健康的营养师&#xff0c;日常大部分时间都在与猫咪和狗狗为伴&#xff0c;对它们入店时的身体状况往往能迅速做出初步判断。当前&#xff0c;多数家养猫咪面临的肥胖和肝损伤问题尤为突出&#xff0c;尽管医疗干预能缓解病情&#xff0c;但要从根本上解决还…...

股票数据分析(K线图、均值图、MACD图、RSI图)--股票日数据

数据 数据是上证指数日行情数据&#xff0c;股票代码000002.sz&#xff0c;原始数据shdata示例如下&#xff1a; 读取数据&#xff1a; import numpy as np import pandas as pd import mplfinance as mpf import matplotlib.pyplot as plt from datetime import datetime imp…...

重写equals()方法为什么同时要重写hashcode()

equals()方法 equals()方法是Object类中的一个方法&#xff0c;在Object类中&#xff0c;equals等同于。 在不同的类中&#xff0c;往往会对equals()按需求进行重写。重写的目的都是&#xff1a;用于比较两个对象是否 "相等"。如果两个对象的内容相同&#xff0c;那…...

安全及应用(更新)

一、账号安全 1.1系统帐号清理 #查看/sbin/nologin结尾的文件并统计 [rootrootlocalhost ~]# grep /sbin/nologin$ /etc/passwd |wc -l 40#查看apache登录的shell [rootrootlocalhost ~]# grep apache /etc/passwd apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin#改变…...

Hadoop权威指南-读书笔记-03-Hadoop分布式文件系统

Hadoop权威指南-读书笔记 记录一下读这本书的时候觉得有意思或者重要的点~ 还是老样子~挑重点记录哈&#x1f601;有兴趣的小伙伴可以去看看原著&#x1f60a; 第三章 Hadoop分布式文件系统 当数据集的大小超过一台独立的物理计算机的存储能力时&#xff0c;就有必要对它进行分…...

Rust入门实战 编写Minecraft启动器#2建立资源模型

首发于Enaium的个人博客 我们需要声明几个结构体来存储游戏的资源信息&#xff0c;之后我们需要将json文件解析成这几个结构体&#xff0c;所以我们需要添加serde依赖。 serde { version "1.0", features ["derive"] }资源相关asset.rs use serde::De…...

小白学C++(第一天)基础入门

温馨提醒&#xff1a;本篇文章&#xff0c;请各位c基础不行的童鞋不要贸然观看 C的第一个程序 第一个关键字namespace namespace 是定义空间的名字的关键字&#xff0c;使用格式格式如下&#xff1a; namespace 空间名 { } 其中{ }内的命名空间的成员&#xff0c;可以定义…...

谷歌正在试行人脸识别办公室安全系统

内容提要&#xff1a; &#x1f9ff;据美国消费者新闻与商业频道 CNBC 获悉&#xff0c;谷歌正在为其企业园区安全测试面部追踪技术。 &#x1f9ff;测试最初在华盛顿州柯克兰的一间办公室进行。 &#x1f9ff;一份内部文件称&#xff0c;谷歌的安全和弹性服务 (GSRS) 团队将…...

【CSS01】CSS概述,使用样式的必要性,CSS语法及选择器

文章目录 一、什么是样式二、使用样式的必要性三、使用样式的几种方式四、CSS基本语法&#xff1a;五、CSS的注释六、CSS选择器——重点相关单词 一、什么是样式 概念&#xff1a; Cascade [kˈskeɪd] Style Sheet [ʃiːt] 级联样式单/表&#xff0c;层叠样式表 CSS有化腐…...

PostgreSQL的pg_bulkload工具

PostgreSQL的pg_bulkload工具 pg_bulkload 是一个针对 PostgreSQL 提供高性能批量数据加载的工具。相较于内置的 COPY 命令&#xff0c;pg_bulkload 更加灵活并且在许多情况下性能更高。它支持数据的强制加载、数据过滤、数据转换以及错误处理等多种功能&#xff0c;非常适合需…...

【Java伴学笔记】Day-01 命令行|环境|编译解释运行|Java的相关分支|Java的特性|字面量

一、关于命令行 图形化界面的缺点 需要加载图片等一系列资源 效率较低 命令行 CMDMicrosoft Learn-CMDWindows CMD常用命令大全&#xff08;值得收藏&#xff09; 二、环境 什么是JDK JDK是Java Development Kit的缩写&#xff0c;意为Java开发工具包。它是一个用于开发Java应用…...

如何使用Vue3创建在线三维模型展示?

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 代码相关的技术博客 代码应用场景介绍 本段代码使用 RoughJS 库在 HTML5 Canvas 上创建了手绘风格的图像&#xff0c;展示了 RoughJS 库的强大功能&#xff0c;可用于创建具有有机手绘外观的图形。 代码基本…...

使用ndoe实现自动化完成增删改查接口

使用ndoe实现自动化完成增删改查接口 最近工作内容比较繁琐&#xff0c;手里需要开发的项目需求比较多&#xff0c;常常在多个项目之间来回切换&#xff0c;有时候某些分支都不知道自己开发了什么、做了哪些需求&#xff0c; 使用手写笔记的方式去记录分支到头来也是眼花缭乱&a…...

排序 -- 手撕归并排序(递归和非递归写法)

一、基本思想 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法,该算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使每个子序列有…...

防火墙基础及登录(华为)

目录 防火墙概述防火墙发展进程包过滤防火墙代理防火墙状态检测防火墙UTM下一代防火墙&#xff08;NGFW&#xff09; 防火墙分类按物理特性划分软件防火墙硬件防火墙 按性能划分百兆级别和千兆级别 按防火墙结构划分单一主机防火墙路由集成式防火墙分布式防火墙 华为防火墙利用…...

【Vue】使用html、css实现鱼骨组件

文章目录 预览图组件测试案例 预览图 组件 <template><div class"context"><div class"top"><div class"label-context"><div class"label" v-for"(item, index) in value" :key"index&qu…...

Python的多态

在 Python 中&#xff0c;多态&#xff08;Polymorphism&#xff09;是指不同的对象可以对相同的消息&#xff08;方法调用&#xff09;做出不同的响应。 简单来说&#xff0c;多态允许使用一个统一的接口来操作不同类型的对象&#xff0c;而这些对象会根据自身的类型来执行相应…...

001uboot体验

1.uboot的作用&#xff1a; 上电->uboot启动->关闭看门狗、初始化时钟、sdram、uart等外设->把内核文件从flash读取到SDRAM->引导内核启动->挂载根文件系统->启动根文件系统的应用程序 2.uboot编译 uboot是一个通用的裸机程序&#xff0c;为了适应各种芯片&…...

Flask之电子邮件

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、使用Flask-Mail发送电子邮件 1.1、配置Flask-Mail 1.2、构建邮件数据 1.3、发送邮件 二、使用事务邮件服务SendGrid 2.1、注册SendGr…...