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

《剑指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;//释放首节点,并重新指向新指针nextfree(*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指针指向posprev->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->nextprev->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->nextSLTNode* 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再移动。


回顾:

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

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

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

http://www.dtcms.com/a/449201.html

相关文章:

  • 网站备案号怎么查询做设计私活的网站
  • 微信小程序入门学习教程,从入门到精通,WXS语法详解(10)
  • 深圳做网站公司哪家好在线绘画网站
  • CodeX CLI安装+MCP适配与VSCode部署(Win)
  • 手写MyBatis第95弹:异常断点精准捕获MyBatis深层BUG
  • 网站的结构是什么样的鹰潭律师网站建设
  • Rust多线程详解
  • tcp 服务器的设计思路
  • 基础架构安全和云原生安全的融合~K8S安全和传统安全~K8S和安全融合~综合安全大饼
  • Python全栈(基础篇)——Day05:后端内容(dict与set+while循环+for循环+实战演示+每日一题)
  • 建设网站用什么软件排版网站建设技术和销售工资
  • UNIX下C语言编程与实践31-UNIX 进程执行新程序:system 函数的使用与内部实现机制
  • 【Java核心技术/多线程】35道Java多线程面试题与答案
  • 【AI智能体】Coze 打造AI数字人视频生成智能体实战详解
  • 网站开发外键邯郸网站开发定制
  • FreeRTOS任务同步与通信--事件标志组
  • Excel基础知识 - 导图笔记
  • Flink 执行模式在 STREAMING 与 BATCH 之间做出正确选择
  • 杭州网站制作平台公司医院网站建设存在问题
  • Python中*args与**kwargs用法解析
  • 【大模型】多智能体架构详解:Context 数据流与工作流编排的艺术
  • 描述逻辑(Description Logic)对自然语言处理深层语义分析的影响与启示
  • python爬虫(三) ---- 分页抓取数据
  • 探索大语言模型(LLM):大模型微调方式全解析
  • 【学习笔记03】C++STL标准模板库核心技术详解
  • 做网站有什么关于财务的问题网络设计工作
  • P9751 [CSP-J 2023] 旅游巴士
  • 宠物用品网站开发背景网站推广设计
  • MySql复习及面试题学习
  • .NET周刊【9月第2期 2025-09-14】