【数据结构入坑指南(三.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

全栈分页方案:MyBatisPlus后端与Thymeleaf前端深度整合指南

全栈分页方案:MyBatisPlus后端与Thymeleaf前端深度整合指南

目录 前言 一、MybatisPlus搭建及表介绍 1、MybatisPlus环境搭建 2、示例表结构介绍 二、Java后台分页实现 1、实体类实现 2、业务层分页实现 3、控制层实现 三、Thymeleaf分页集成 1、分页表格展示 2、分页条集成 3、成果展示 四、可能遇到的问题 1、分页不展示 2、问题解决 五、总结 前言         在当今的软件开发中,分页功能是提升用户体验和系统性能的关键。无论是企业级应用还是面向用户的平台,高效分页都能显著改善交互体验。今天将带你深入了解如何通过 MyBatisPlus 和 Thymeleaf 的深度整合,打造一个完整的全栈分页解决方案。分页功能不仅能够提升用户交互的流畅性,还能显著降低服务器的负载,提高系统的整体性能。将 MyBatisPlus 和 Thymeleaf

By Ne0inhk
Flutter 三方库 web_scraper 轻量级网页抓取核心适配进阶:精通跨端选择器表达式无头浏览器代理、极限提取残缺数据接口网格实现鸿蒙万物互联泛信息-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 web_scraper 轻量级网页抓取核心适配进阶:精通跨端选择器表达式无头浏览器代理、极限提取残缺数据接口网格实现鸿蒙万物互联泛信息-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 web_scraper 轻量级网页抓取核心适配进阶:精通跨端选择器表达式无头浏览器代理、极限提取残缺数据接口网格实现鸿蒙万物互联泛信息即时采集 前言 在 OpenHarmony 应用开发中,我们并非总能获得完美的后端 API。当我们希望在鸿蒙应用中聚合一些公开的技术资讯、天气指数或是论坛热帖,但对方并未提供标准化 JSON 接口时,通过抓取网页(Web Scraping)获取结构化数据成了唯一的出路。web_scraper 库为 Flutter 开发者提供了一套基于 CSS 选择器的极简网页爬虫方案。本文将实战介绍如何在鸿蒙端利用该库构建一个高效的信息采集底座。 一、原直线性 / 概念介绍 1.1 基础原理/概念介绍 web_scraper 的核心逻辑是基于 HTTP 内容请求与 HTML DOM 树的解析映射。

By Ne0inhk
路径规划算法仿真 A星算法 传统A*(Astar)算法+改进后的A*算法 Matlab代码 可...

路径规划算法仿真 A星算法 传统A*(Astar)算法+改进后的A*算法 Matlab代码 可...

路径规划算法仿真 A星算法 传统A*(Astar)算法+改进后的A*算法 Matlab代码 可以固定栅格地图与起点终点 可以进行定量比较 改进: ①提升搜索效率(引入权重系数) ②冗余拐角优化(可显示拐角优化次数) ③路径平滑处理(引入梯度下降算法配合S-G滤波器) 代码含注释! 概述 本文介绍了一个基于 MATLAB 的 A* 路径规划算法实现,该算法能够在包含随机障碍物的栅格地图中找到从起点到终点的最优路径。代码提供了完整的路径规划解决方案,包括环境生成、算法执行、路径优化和可视化展示。 系统功能 1. 环境生成与初始化 系统能够创建自定义大小的栅格地图,并随机生成障碍物: n = 100; % 100x100 的栅格地图 wallpercent = 0.4; % 障碍物占比 40% [field, startposind, goalposind, costchart, fieldpointers] = initializeField(n,

By Ne0inhk
全场景教育 AI 助手诞生,Web + 小程序 + 实时同步,随时随地想用就用

全场景教育 AI 助手诞生,Web + 小程序 + 实时同步,随时随地想用就用

⭐️个人主页:秋邱-ZEEKLOG博客 📚所属栏目:python 序章:一场 “多端协同” 的探险之旅 经过前 7 期迭代,成绩预测平台已进化为 “智能教学助手”,但新的 “场景壁垒” 出现了: * 教师在办公室需要 Web 端批量处理数据,却只能用电脑; * 家长接送孩子时想查看成绩,打开电脑太麻烦; * 学生在家用平板学习,却同步不了学校的预测记录。 这一期,我们开启 “多端协同探险”,目标是打破设备边界 —— 打造 “Web 端管理后台 + 微信小程序 + 数据实时同步” 的全场景体系,让教师、家长、学生随时随地能用,实现 “一处操作,多端同步” 的终极体验! 探险地图:三大关卡 + 通关目标 探险关卡 核心任务 通关标准 目标用户 第一关:Web

By Ne0inhk