【数据结构入坑指南(三.2)】--《剑指Offer:单链表操作入门——从“头删”开始破解面试》

【数据结构入坑指南(三.2)】--《剑指Offer:单链表操作入门——从“头删”开始破解面试》

🔥@晨非辰Tong:个人主页 

👀专栏:《C语言》《数据结构与算法》

💪学习阶段:C语言、数据结构与算法初学者

⏳“人理解迭代,神理解递归。”


引言:上篇我们初探了单链表的“不连续”之美,并实现了部分基础操作。本篇将作为单链表的终极篇章,彻底攻克其余核心功能,助你构建完整知识体系。

目录

二、单链表的实现(续)

2.5  单链表的头删

三、何时用顺序表,何时用单链表?

3.1  顺序表功能的复杂度

3.2  单链表功能的复杂度

四、链表其余功能

4.1  查找

4.2  在指定位置之前插入

4.3  在指定位置之后插入

4.4  删除pos节点

4.5  删除pos之后的节点

4.6  销毁链表


二、单链表的实现(续)

2.5  单链表的头删

--实现头删的算法思路很简单,不同于前面实现的尾删函数:

        由于定义的 phead 指向的是首节点(保存首节点的地址),就需要改变 phead 的指向—>第2节点。

        那么,要定义一个新的指针 next 将第2节点的地址保存(因为释放*pphead,会将首节点next指针保存的第2节点的地址销毁),接下来直接释放首节点,再将phead指向创建的next指针(*pphead)->next = next。
SList.c文件 include "SList.h" //定义_头删 void SLTPopFront(SLTNode** pphead) { //链表为空不能删除 assert(pphead && *pphead);//pphead为空—>解引用错误;*pphead为空->首节点为空(链表为空),无法删除 //定义新指针,将第2个节点第地址存起来,释放phead指向的第一个的节点 SLTNode* next = (*pphead)->next; //释放首节点,并重新指向新指针next free(*pphead); //(*pphead)存放的是首节点的地址 (*pphead) = next; } 
test.c文件 include "SList.h" void test02() { //创建空链表 SLTNode* phead = NULL;//phead头指针,指向的首节点为空,代表链表没有节点 //尾插 SLTPushBack(&phead, 1);//为了使形参的变化影响实参,传地址 SLTPushBack(&phead, 2); SLTPushBack(&phead, 3); SLTPushBack(&phead, 4); SLTPrint(phead); //头删 SLTPopFront(&phead); SLTPrint(phead); SLTPopFront(&phead); SLTPrint(phead); SLTPopFront(&phead); SLTPrint(phead); SLTPopFront(&phead); SLTPrint(phead); } int main() { test02(); return 0; }

三、何时用顺序表,何时用单链表?

前面已经学习了如何判断算法的好坏:时间复杂度、空间复杂度!

下面通过对前面实现的顺序表、单链表插入、删除算法的复杂度计算来对比。

--复杂度主要关注时间复杂度

3.1  顺序表功能的复杂度

顺序表的尾插算法:

--程序中没有循环结构(循环次数未知),时间复杂度—>O(1)。
顺序表的头插算法:

--程序中调用了扩容函数(无循环),看函数主体——存在循环次数未知的循环结构,时间复杂度——>O(N)。
顺序表的尾删算法:

--代码没有循环结构,时间复杂度——>O(1)。
顺序表的头删算法:

--存在循环次数未知
的循环结构,时间复杂度——>O(N)。

3.2  单链表功能的复杂度

单链表的尾插算法:

--存在循环次数未知的循环结构,时间复杂度——>O(N)。
单链表的头插算法:

--代码没有循环结构,时间复杂度——>O(1)。
单链表的尾删算法:

--存在循环次数未知的循环结构,时间复杂度——>O(N)。
单链表的头删算法:

--代码没有循环结构,时间复杂度——>O(1)。

通过对比发现:频繁的对数据头部进行插入、删除:使用单链表;频繁的对数据尾部进行插入、删除:使用顺序表。

四、链表其余功能

4.1  查找

SList.c include "SList.c" //定义_查找 SLTNode* SLTFind(SLTNode* phead, SLTDataType x) { //创建指针接收首节点 SLTNode* pcur = phead; while (pcur) { if (pcur->data == x) { return pcur;//返回找到的数值 } pcur = pcur->next; } //未找到 return NULL;//返回空 } //返回类型为SLTNode* ,因为最后程序返回的数据是这个类型
test.c include "SList.h" void test02() { //创建空链表 SLTNode* phead = NULL;//phead头指针,指向的首节点为空,代表链表没有节点 //尾插 SLTPushBack(&phead, 1);//为了使形参的变化影响实参,传地址 SLTPushBack(&phead, 2); SLTPushBack(&phead, 3); SLTPushBack(&phead, 4); SLTPrint(phead); //查找 SLTNode* pos = SLTFind(phead, 4); if (pos) { printf("找到了!\n"); } else { printf("未找到!\n"); } } int main() { test02(); return 0; } 
--实现查找算法的思路很简单,需要遍历数组与目标查找数值进行对比。

4.2  在指定位置之前插入

SList.c文件 include "SList.h" //定义_在指定位置之前插入 void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) { assert(pphead && pos);//对二级指针解引用不能为空 //新节点存储数据 SLTNode* newcode = SLTBuyNode(x); //pos指向头节点 if (pos == *pphead) { //执行头插 SLTPushFront(pphead, x); } else { //prev从首节点遍历寻找pos的前一个节点(next与pos的地址匹配) SLTNode* prev = *pphead; while (prev->next != pos) { //没找到,移动到下一个节点 prev = prev->next; } //跳出循环——找到了 newcode->next = pos;//新节点的next指针指向pos prev->next = newcode;//前一个节点next指针指向新节点 } }
对于在指定位置之前插入,定义中情况为头插,涉及到首节点的改变,所以要传首节点的地址过去。
test.c文件 include "SList.h" void test02() { //创建空链表 SLTNode* phead = NULL;//phead头指针,指向的首节点为空,代表链表没有节点 //尾插 SLTPushBack(&phead, 1);//为了使形参的变化影响实参,传地址 SLTPushBack(&phead, 2); SLTPushBack(&phead, 3); SLTPushBack(&phead, 4); SLTPrint(phead); SLTNode* pos = SLTFind(phead, 4); //在4前面插入 SLTInsert(&phead, pos, 100); SLTPrint(phead); //在首节点前插入 pos = SLTFind(phead, 1); SLTInsert(&phead, pos, 100); SLTPrint(phead); } int main() { test02(); return 0; }

4.3  在指定位置之后插入

这种就是前面的第2种情况。
SList.c文件 include "SList.h" //定义_在指定位置之后插入 SLTNode* SLTInsertAfter(SLTNode* pos, SLTDataType x) { assert(pos); SLTNode* newcode = SLTBuyNode(x); newcode->next = pos->next;//接收后节点的地址 pos->next = newcode; } 

4.4  删除pos节点

SList.c文件 include "SList.h" //定义_删除pos节点 void SLTErase(SLTNode** pphead, SLTNode* pos) { assert(pphead && *pphead); //如果pos刚好就是头结点 if (pos == *pphead) { SLTPopFront(pphead); } else { SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } //prev pos pos->next prev->next = pos->next; free(pos); pos = NULL; } } 
算法思路:删除就是将节点空间释放,然后将pos前的节点与pos后的节点进行地址链接。

4.5  删除pos之后的节点

SList.c include "SList.h" //删除pos之后的结点 void SLTEraseAfter(SLTNode* pos) { assert(pos&&pos->next); //pos del del->next SLTNode* del = pos->next; pos->next = del->next; free(del); del == NULL; }
算法思路:找到pos节点后,创建新指针接收后节点的地址,再通过next指针进行地址链接。

4.6  销毁链表

SList.c include "SList.h" //销毁 void SListDestroy(SLTNode** pphead) { assert(pphead); SLTNode* pcur = *pphead; while (pcur) { SLTNode* next = pcur->next; free(pcur); pcur = next; } *pphead = NULL; }
算法思路:只需要循环释放pos节点空间,但需要新指针先将pos之后的节点地址保存,pos再移动。

回顾:

《面试高频数据结构:单链表与顺序表之争,读懂“不连续”之美背后的算法思想》

《算法面试“必杀技”:双指针法高效解决数组原地操作》

结语:至此,我们已经为单链表这座「数据结构大厦」打下了坚实的地基。是时候解锁新的关卡了——接下来,我们将进入功能更强大的双向链表,看它如何破解单链表遍历的效率困境,让插入删除更加游刃有余。

Read more

【前端】Vue 组件开发中的枚举值验证:从一个Type属性错误说起

【前端】Vue 组件开发中的枚举值验证:从一个Type属性错误说起

🌹欢迎来到《小5讲堂》🌹 🌹这是《小程序》系列文章,每篇文章将以博主理解的角度展开讲解。🌹 🌹温馨提示:博主能力有限,理解水平有限,若有不对之处望指正!🌹 👨💻 作者简介 🏆 荣誉头衔:2024博客之星Top14 | ZEEKLOG博客专家 | 阿里云专家博主 🎤 经历:曾多次进行线下演讲,亦是 ZEEKLOG内容合伙人 以及 新星优秀导师 💡 信念:“帮助别人,成长自己!” 🚀 技术领域:深耕全栈,精通 .NET Core (C#)、Python、Java,熟悉主流数据库 🤝 欢迎交流:无论是基础概念还是进阶实战,都欢迎与我探讨! 目录 * 前言 * 解决过程 * 一、错误场景还原 * 1.1 错误发生的位置 * 1.2 常见的触发场景 * 二、深入理解 Vue

By Ne0inhk

Flutter 三方库 jwt_io 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、严谨、全能的 JSON Web Token (JWT) 加解密与身份安全验证引擎

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 jwt_io 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、严谨、全能的 JSON Web Token (JWT) 加解密与身份安全验证引擎 在鸿蒙(OpenHarmony)系统的端云一体化登录、政企应用的安全审计或复杂的跨端权限校验场景中,如何确保来自云端授信中心的 JWT Token 既能被正确解析(Decode),又能被严密地校验其合法性与过期时间?jwt_io 为开发者提供了一套工业级的、基于 RFC 7519 标准的 JSON Web Token 深度处理方案。本文将深入实战其在鸿蒙应用安全底座中的应用。 前言 什么是 JWT IO?它不仅是一个简单的 Base64 解码器,而是一个具备深厚 RFC

By Ne0inhk

微算法科技(NASDAQ: MLGO)使用量子傅里叶变换(QFT),增强图像压缩和滤波效率

在人工智能与量子计算深度融合的浪潮中,传统图像处理技术面临效率瓶颈。经典傅里叶变换(DFT)虽能将图像从空间域转换至频率域以实现压缩与滤波,但其计算复杂度随数据规模指数增长,难以满足实时性需求。量子计算凭借量子叠加与纠缠特性,为高频图像处理提供了突破口。微算法科技(NASDAQ :MLGO)敏锐捕捉到量子傅里叶变换(QFT)的潜力——作为经典DFT的量子版本,QFT可在多项式时间内完成频域转换,将图像压缩效率提升指数级,同时通过量子并行性优化滤波精度。这一技术突破为高分辨率遥感、医学影像分析等场景提供了高效解决方案。 量子傅里叶变换(QFT)是量子计算中实现时频域转换的核心算法,其本质是通过量子态叠加与干涉,将输入量子态的相位信息从空间域映射至频率域。 与传统DFT相比,QFT的输出为量子叠加态,需通过测量获取概率分布。其核心优势在于利用量子并行性,将计算复杂度从经典DFT的O(n2n)降至O(n2),同时通过相位旋转门实现相干叠加,突破经典复数乘加运算的线性限制。微算法科技将QFT引入图像处理,通过量子电路设计将图像数据编码为量子态,在频域中高效完成压缩与滤波操作。 量子态编码

By Ne0inhk
【数据结构指南】深入二叉树遍历

【数据结构指南】深入二叉树遍历

前言:         在前文中,我们探讨了完全二叉树和满二叉树的概念与性质,并基于完全二叉树实现了堆这一数据结构。然而,对于普通二叉树的认识仍有待深入,本文将系统性地介绍普通二叉树的遍历相关内容。                   一、前置说明          一般而言,对于一棵普通二叉树是通过链式结构定义,即每个节点包含三个部分:          1.数据域(data):用于存储节点的值          2.左指针(left):用于指向左子节点          3.右指针(right):用于指向右子节点                  typedef int BTDataType;//此处将 (int)整形 作为节点存储的内容 typedef struct BinaryTree { BTDataType data; struct BinaryTree* left; struct BinaryTree* right; }BTNode;                  在学习二叉树的基本操作前,需先要先创建一棵二叉树,然后才能学习

By Ne0inhk