数据结构——链表

数据结构——链表

目录

链表基础知识

链表的概念

基本特点

分类

基本操作(接口)

单链表的实现

定义

创建新节点

增、删、查、改操作

双链表的实现

定义

初始化(创造哨兵节点)

增、删、查、改操作

顺序表和链表的区别


 轻松无痛玩转链表

链表基础知识

链表的概念

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

基本特点

  1. 动态大小:链表的大小可以在运行时动态变化,不需要在创建时指定固定的大小。
  2. 元素连接:每个元素(通常称为节点)存储数据,并包含一个或多个指针指向列表中的其他节点。
  3. 无需连续内存:由于元素通过指针连接,它们不需要在内存中连续存放,这使得内存使用更加灵活。
  4. 插入和删除操作:在链表中插入或删除节点通常比数组更快,因为这些操作不需要移动其他元素。

分类

1.单向与非单向

 2.带头与非带头(有没有哨兵节点)

哨兵节点就是一个存储非有效数据的虚拟节点,他不是数据结合的一部分,可以简化删除操作,避免空链表,统一处理逻辑。

3.循环与非循环

常见的链表就有不带头单向非循环链表带头双向循环链表。

基本操作(接口)

  • 插入:在链表的特定位置添加新节点。
  • 删除:移除链表中的特定节点。
  • 搜索:查找链表中的特定值。
  • 遍历:按顺序访问链表的所有节点。

所以总的来说,链表适用于当数据结合频繁变化,需要快速插入和删除,内存空间分散的场景。

单链表的实现

单链表也就是不带头单向非循环链表。

定义

//方便后续更改类型 typedef int SLTDataType; //定义链表节点 typedef struct SListNode { SLTDataType data; struct SListNode* next; }SLTNode;

创建新节点

//创建新节点 SLTNode* SLTCreateNode(SLTDataType x) { //malloc出一个节点 SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode == NULL) { perror("malloc"); exit(1); } //赋值 newnode->data = x; newnode->next = NULL; return newnode; }

增、删、查、改操作

尾插

只需要在链表末尾增添节点,修改指针就可以了。

//尾插 void SLTPushBack(SLTNode** pphead, SLTDataType x) { assert(pphead); //先创造一个新节点 SLTNode* newnode = SLTCreateNode(x); //空链表的情况 if (*pphead == NULL) { *pphead = newnode; } else { //非空链表 //找到尾节点 SLTNode* ptail = *pphead; while (ptail->next) { ptail = ptail->next; } ptail->next = newnode; } }

细节:因为涉及到phead指针的修改,所以参数得传二级指针。

(*pphead == phead)

找到尾节点需要判断当前节点的下一节点是否为空。(ptail->next)

所以总的来说,尾插需要注意三点。

1.不要忘记链表为空的情况。

2.传phead的二级指针。

3.找尾节点。

头插

头插非常简单,创建新节点,然后链接在链表头部就行。

//头插 void SLTPushFront(SLTNode** pphead, SLTDataType x) { assert(pphead); //先创造一个新节点 SLTNode* newnode = SLTCreateNode(x); newnode->next = *pphead; *pphead = newnode; }

尾删

要注意判断只有一个节点的情况和判断链表为空的情况(assert(pphead && *pphead))。

//尾删 void SLTPopBack(SLTNode** pphead) { assert(pphead && *pphead); //只有一个节点的情况 if ((*pphead)->next == NULL)//->运算符优先级高于* { free(*pphead); *pphead = NULL; } else { //找到尾节点以及上一个节点 SLTNode* ptail = *pphead; SLTNode* prev = *pphead; while (ptail->next) { prev = ptail; ptail = ptail->next; } prev->next = NULL; free(ptail); ptail = NULL; } }

头删

删除头节点前要把下一个节点存储起来,还是要注意判断链表为空的情况。

//头删 void SLTPopFront(SLTNode** pphead) { assert(pphead && *pphead); SLTNode* newhead = (*pphead)->next; free(*pphead); *pphead = newhead; }

在pos位置之前插入

注意:

1.注意判断空链表,不能再空节点前插入。

2.pos不能为空。

3.pos是头节点的情况。

//在指定位置之前插入数据 void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) { //链表不能为空链表,不能在一个空节点前插入一个节点 assert(pphead && *pphead); //不能插入一个空节点 assert(pos); //pos是头节点的情况,头插 if (*pphead == pos) { SLTPushFront(pphead, x); } else { SLTNode* newnode = SLTCreateNode(x); //找到pos的前一个节点 SLTNode* prev = *pphead; while (prev->next != pos)//如果pos是头节点,那么这个循环就达不到目的 { prev = prev->next; } newnode->next = pos; prev->next = newnode; } }

在pos位置之后插入

//在指定位置之后插入数据 void SLTInsertAfter(SLTNode* pos, SLTDataType x) { assert(pos); SLTNode* newnode = SLTCreateNode(x); //下面两行代码顺序不能乱 newnode->next = pos->next; pos->next = newnode; }

注意判断pos位置不能为空。

查找

遍历链表就可以了

//查找 SLTNode* SLTfind(SLTNode* phead, SLTDataType x) { //空节点返回null,不用assert SLTNode* pcur = phead; while (pcur) { if (pcur->data == x) { return pcur; } pcur = pcur->next; } return NULL; }

删除pos节点

找到pos的下一个节点,pos的前一个节点,更改指针,free掉pos节点就行了。

注意判断链表为空和pos节点为空的情况。

//删除pos节点 void SLTErase(SLTNode** pphead, SLTNode* pos) { assert(pphead && *pphead); assert(pos); //pos是头节点 if (pos == *pphead) { SLTPopFront(pphead); } else { SLTNode* prev = *pphead; while (prev->next != pos)//如果pos是头节点,那么这个循环就达不到目的 { prev = prev->next; } prev->next = pos->next; free(pos); pos = NULL; } }

删除pos之后的节点

注意判断pos之后的节点是不是空节点。

//删除pos之后的节点 void SLTEraseAfter(SLTNode* pos) { assert(pos && pos->next); SLTNode* del = pos->next; pos->next = del->next; free(del); del = NULL; }

销毁链表

遍历链表就行,把要删除的节点提前存起来就行,注意判断链表为空的情况。

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

双链表的实现

定义

typedef int LTDataType; typedef struct ListNode { LTDataType data; struct ListNode* next; struct ListNode* prev; }LTNode;

初始化(创造哨兵节点)

//申请一个节点 LTNode* CreateNode(LTDataType x) { LTNode* node = (LTNode*)malloc(sizeof(LTNode)); if (node == NULL) { perror("malloc fail!"); exit(1); } node->data = x; //双向链表是带头双向循环的,所以初始情况哨兵节点前指针和后指针要指向自己,不然不是循环 node->next = node->prev = node; return node; } //初始化 创造一个哨兵位 void LTinit(LTNode** pphead) { // *pphead = CreateNode(-1); }

增、删、查、改操作

尾插

​ //尾插 void LTPushBack(LTNode* phead, LTDataType x) { //双向链表是空链表时,指的是只有一个哨兵位 //保证链表不是空链表 assert(phead); //创造新节点 LTNode* newnode = CreateNode(x); //有效节点的最后一个节点通过phead的prev指针找到 LTNode* ptail = phead->prev; //新节点的前指针指向有效节点的最后一个节点ptail newnode->prev = ptail; //新节点的后指针指向头节点phead newnode->next = phead; ptail->next = newnode; phead->prev = newnode; } ​

 

细节:这里的phead不用传二级指针,因为不涉及phead的改变,包括接下来的头插也是。

头插

头插指的是插入到第一个有效节点的前面,也就是哨兵位节点的后面。

//头插,插在哨兵位之后,第一个有效节点之前 void LTPushFront(LTNode* phead, LTDataType x) { //保证有一个哨兵位 assert(phead); LTNode* newnode = CreateNode(x); newnode->prev = phead; newnode->next = phead->next; phead->next->prev = newnode; phead->next = newnode; }

尾删

删除有效节点的最后一个节点,注意判断链表为空的情况(没有有效节点,只有哨兵位)。

//尾删,删的是有效节点 void LTPopBack(LTNode* phead) { //链表不能为空且得有有效节点 assert(phead && phead->next != phead); LTNode* del = phead->prev; del->prev->next = phead; phead->prev = del->prev; free(del); del = NULL; }

头删

删除有效节点的第一个节点,注意判断链表为空的情况。

//头删 void LTPopFront(LTNode* phead) { assert(phead && phead->next != phead); LTNode* del = phead->next; del->next->prev = phead; phead->next = del->next; free(del); del = NULL; }

在pos位置之后插入数据    

//在pos位置之后插入数据 void LTInsert(LTNode* pos, LTDataType x) { assert(pos); LTNode* newnode = CreateNode(x); newnode->prev = pos; newnode->next = pos->next; //这里顺序不能颠倒 pos->next->prev = newnode; pos->next = newnode; }

注意后两句代码的顺序是一定不能颠倒的。

查找

寻找指定数据,返回该节点,遍历链表即可。

 注意判断链表为空的情况。

//找指定数据 LTNode* LTFind(LTNode* phead, LTDataType x) { assert(phead && phead->next != phead); LTNode* pcur = phead->next; while (pcur != phead) { if (pcur->data == x) { return pcur; } pcur = pcur->next; } return NULL; }

删除pos节点

//删除pos节点 void LTErase(LTNode* pos) { assert(pos); pos->prev->next = pos->next; pos->next->prev = pos->prev; free(pos); pos = NULL; }

销毁链表

//销毁链表 void LTDesTroy(LTNode* phead) { //从一个有效节点开始销毁 LTNode* pcur = phead->next; while (pcur != phead) { LTNode* next = pcur->next; free(pcur); pcur = next; } free(phead); phead = NULL; }

遍历链表,逐个销毁,然后销毁哨兵位就行了。

顺序表和链表的区别


拜拜,下期再见😏

摸鱼ing😴✨🎞

Read more

AI之MLMs之Tool:Open Notebook的简介、安装和使用方法、案例应用之详细攻略

AI之MLMs之Tool:Open Notebook的简介、安装和使用方法、案例应用之详细攻略

AI之MLMs之Tool:Open Notebook的简介、安装和使用方法、案例应用之详细攻略 目录 Open Notebook的简介 1、特点 (1)、与 Google Notebook LM 的核心优势对比 (2)、核心功能 (Core Capabilities) (3)、高级功能 (Advanced Features) Open Notebook的安装和使用方法 1、安装 1.1、准备工作 1.2、安装步骤 方法一:使用 Docker 命令 (适用于本地或远程服务器) 方法二:使用 Docker Compose (推荐,便于管理) 1.3、关键设置说明 2、使用方法

By Ne0inhk
AI Agent 架构:基础组成模块深度解析

AI Agent 架构:基础组成模块深度解析

AI Agent 架构:基础组成模块深度解析 📝 本章学习目标:本章是入门认知部分,帮助零基础读者建立对AI Agent的初步认知。通过本章学习,你将全面掌握"AI Agent 架构:基础组成模块深度解析"这一核心主题。 一、引言:为什么这个话题如此重要 在AI Agent快速发展的今天,AI Agent 架构:基础组成模块深度解析已经成为每个开发者和研究者必须了解的核心知识。无论你是技术背景还是非技术背景,理解这一概念都将帮助你更好地把握AI时代的机遇。 1.1 背景与意义 💡 核心认知:AI Agent正在从"对话工具"进化为"执行引擎",能够主动完成任务、调用工具、与外部世界交互。这一变革正在深刻改变我们的工作和生活方式。 从2023年AutoGPT的横空出世,到如今百花齐放的Agent生态,短短一年多时间,执行式AI已经从概念走向落地。根据最新统计,

By Ne0inhk
task:全网最牛的AI 白嫖教程,用 trae “套娃”安装Claude code

task:全网最牛的AI 白嫖教程,用 trae “套娃”安装Claude code

task:全网最牛的AI 白嫖教程,用 trae “套娃”安装Claude code 背景 之前一直没有动手处理 AI 编程软件的事情,一直还停留在拉取 github 然后本地安装的“刻板映像”中,而实际情况是在我拥有 AI-IDE 窗口之后,很多工具都可以互相接通,所以我从最开始下载cursor 安装,逐渐转换为cursor 只是我的一个窗口,最终目的是用 安装Claude code。 描述 认知跃迁,从“本地安装工具”的静态思维 → 转向“AI-IDE 为统一入口”的动态集成范式。本质是将 Cursor 视为「AI 编程操作系统」的 Shell,而非终点。核心转变:工具即服务,窗口即接口。 准备怎么干 摸黑开始,

By Ne0inhk
内存暴涨700%背后的惊天真相:AI正在吞噬一切!能源·隐私·绿色三大维度深度拆解

内存暴涨700%背后的惊天真相:AI正在吞噬一切!能源·隐私·绿色三大维度深度拆解

🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习 🎬擅长领域:驱动开发,嵌入式软件开发,BSP开发 ❄️作者主页:一个平凡而乐于分享的小比特的个人主页 ✨收录专栏:未来思考,本专栏结合当前国家战略和实时政治,对未来行业发展的思考 欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖 🔥内存暴涨700%背后的惊天真相:AI正在吞噬一切!能源·隐私·绿色三大维度深度拆解 |前言| 最近装机的小伙伴们欲哭无泪:DDR5内存价格一路狂飙,部分DRAM现货价格在过去一年暴涨近700% 。大家习惯性吐槽“厂商放火”、“产能不足”,但很少有人看到,这场涨价风暴的真正推手,是那只名为“AI”的巨兽。 当你还在为多花几百块钱买内存心疼时,国家正在西部荒漠建起一座座数据中心,科技巨头正在为“吃电怪兽”抢购每一颗芯片。2026年,大型科技公司的AI相关投资预计将达到6500亿美元,较去年增长约80% 。 今天,我们从能源供应、隐私安全、绿色AI 三个维度,结合东数西算、算电协同、

By Ne0inhk