【数据结构】_以单链表为例分析各种方法实现的特殊情况考虑思路

ops/2025/2/1 17:14:48/

目录

1. 尾插SLTPushBack

2. 头插SLTPushFront

3. 尾删SLTPopBack

4. 头删SLTPopFront

5. 指定位置前插入

6. 指定位置前删除


对于每一种方法的具体实现,都不能仅简单考虑链表具有多个结点的情况,对于空链表等特殊情况都需特殊情况特殊分析,才能保证不出现空指针解引用等情况。

现以某几个方法为例,分析特殊情况的考虑思路。

1. 尾插SLTPushBack

1、考虑具有若干结点的情况,创建结构体指针变量curNode,令其遍历现有链表直至指向一个后继指针域为NULL的结点为止。令该后继指针域为NULL即可:

void SLTPushBack(SLTNode** pphead, SLTDataType x) {SLTNode* curNode = *pphead;while (curNode->next) {curNode = curNode->next;}curNode->next = newNode;
}

2、由于需对pphead进行解引用操作,则需保证其不为空,增加断言:

assert(pphead);

3、由于为增加结点操作,原链表可为空,无需对*pphead进行断言。

4、由于为增加结点操作,考虑当前链表为空的情况,即*pphead=NULL,又将*pphead复制给了curNode,故curNode=NULL,若采取与普通链表即情况1的相同处理模式,则遍历链表找尾结点的while (curNode->next)操作会导致空指针的解引用操作,故需对原链表是否为空进行单独讨论

讨论逻辑为:直接将创建的新结构体变量赋值给链表的头指针*pphead即可:

void SLTPushBack(SLTNode** pphead, SLTDataType x) {assert(pphead);SLTNode* newNode = SLTCreatNode(x);// 空链表if (*pphead == NULL) {*pphead = newNode;}else {// 非空链表SLTNode* curNode = *pphead;while (curNode->next) {curNode = curNode->next;}curNode->next = newNode;}
}

2. 头插SLTPushFront

1、考虑具有若干结点的链表:

创建新结点,令新结点的后继指针域指向原头结点,再令新结点为链表的头结点:

void SLTPushFront(SLTNode** pphead, SLTDataType x) {SLTNode* newNode = SLTCreatNode(x);newNode->next = *pphead;// 令新结点为链表的头结点*pphead = newNode;
}

 2、由于需对pphead进行解引用操作,故需保证pphead不为空,增加断言:

assert(pphead);

3、由于为增加结点操作,故原链表可为空,即可无结点,即允许*pphead=NULL,无需断言;

4、由于为增加结点操作,考虑当前链表为空的情况,即*pphead=NULL,若采取与普通链表相同的处理模式,即创建新结点后令其指针域指向空,再令新结点为新的头结点,并无差错,无需单独讨论处理:

void SLTPushFront(SLTNode** pphead, SLTDataType x) {assert(pphead);SLTNode* newNode = SLTCreatNode(x);newNode->next = *pphead;// 令新结点为链表的头结点*pphead = newNode;
}

3. 尾删SLTPopBack

1、考虑具有若干结点的链表:

遍历找到尾结点及尾结点的前趋结点,释放尾结点并令尾结点的前趋结点的指针域为空;

void SLTPopBack(SLTNode** pphead) {SLTNode* tailPrevNode = *pphead;SLTNode* tailNode = *pphead;while (tailNode->next) {tailPrevNode = tailNode;tailNode = tailNode->next;}free(tailNode);tailNode = NULL;tailPrevNode->next = NULL;
}

2、由于需对pphead进行解引用,需保证pphead不为空:

3、由于要删除结点,故链表不可无结点,需保证*pphead不为空;

结合1、2点,增加断言:

assert(pphead && *pphead);

4、由于要删除结点,考虑删除后链表为空即链表仅有一个结点的情况

若采取同普通链表相同的处理逻辑,则遍历找尾结点的while (tailNode->next)操作会造成空指针解引用,故而该情况需单独讨论。

处理逻辑为:直接释放链表的唯一结点*pphead,并将其置空。无需遍历查找:

void SLTPopBack(SLTNode** pphead) {assert(pphead && *pphead);// 链表只有一个结点if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;}// 链表有多个结点else {SLTNode* tailPrevNode = *pphead;SLTNode* tailNode = *pphead;while (tailNode->next) {tailPrevNode = tailNode;tailNode = tailNode->next;}free(tailNode);tailNode = NULL;tailPrevNode->next = NULL;}
}

4. 头删SLTPopFront

1、考虑具有若干结点的链表,创建结构体指针记录链表第二个结点的地址(链表头结点的后继指针域),释放头结点,令第二个结点为新的头结点:

void SLTPopFront(SLTNode** pphead, SLTDataType x) {SLTNode* secNode = (*pphead)->next;free(*pphead);*pphead = secNode;
}

2、由于需对pphead进行解引用,故需保证pphead不为空;

3、由于是删除结点操作,则需保证链表不能无结点,即链表不为空,即*pphead不为空;

结合2、3,增加断言:

	assert(pphead && *pphead);

4、由于是删除结点操作,考虑删除后链表为空(即链表只有一个结点)的情况:

若采取普通链表相同的处理逻辑,则链表头结点的后继指针域为NULL,再将NULL作为链表的新的头指针,并无差错,无需单独讨论,使用普通链表的处理逻辑即可。

5. 指定位置前插入

1、考虑链表具有若干结点的情况:

创建新结点,找到指定结点pos的前驱结点posPrevNode,令posPrevNode结点的后继指针域指向新结点,再令新结点的后继指针域指向pos结点:

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {SLTNode* newNode = SLTCreatNode(x);SLTNode* posPrevNode = *pphead;while (posPrevNode->next != pos) {posPrevNode = posPrevNode->next;}posPrevNode->next = newNode;newNode->next = pos;
}

2、由于需对pphead解引用,故pphead不可为空。

3、由于为指定位置前增加结点,若链表为空或指定位置为空则无意义,故*pphead与pos均不为空。增加断言:

assert(pphead && *pphead && pos);

4、由于为指定位置前增加结点,最特殊的情况是指定位置为头结点,考虑pos=*pphead的情况,若采取与普通链表相同的处理模式,则无法找到某一结点posPrevNode的后继指针域指向*pphead,故而pos=*pphead的情况须单独讨论处理。

处理逻辑为:当pos=*pphead时,即向链表进行头插,调用已完成的头插方法即可:

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {assert(pphead && *pphead && pos);SLTNode* newNode = SLTCreatNode(x);if (pos == *pphead) {// 调用头插方法SLTPushFront(pphead, x);}else {SLTNode* posPrevNode = *pphead;while (posPrevNode->next != pos) {posPrevNode = posPrevNode->next;}posPrevNode->next = newNode;newNode->next = pos;}
}

6. 指定位置前删除

1、考虑链表具有若干结点的情况:

遍历查找指定位置结点的前驱结点posPrevNode,令其后继指针域指向pos的后继结点,即

posPrevNode->next = pos->next即可:

2、由于需对pphead进行解引用操作,故pphead不可为空。

3、由于为指定位置前删除结点,若链表为空或指定位置为空则无意义,故*pphead与pos均不为空。增加断言:

assert(pphead && *pphead && pos);

4、由于为指定位置前删除结点,最特殊的情况是指定位置为头结点,考虑pos=*pphead的情况,若采取与普通链表相同的处理模式,则无法找到某一结点posPrevNode的后继指针域指向*pphead,故而pos=*pphead的情况须单独讨论处理。

处理逻辑为:当pos=*pphead时,即向链表进行头删,调用已完成的头删方法即可:

void SLTErase(SLTNode** pphead, SLTNode* pos) {assert(pphead && *pphead && pos);if (*pphead == pos) {// 调用头删方法SLTPopFront(pphead);}else {SLTNode* posPrevNode = *pphead;while (posPrevNode->next != pos) {posPrevNode = posPrevNode->next;}posPrevNode->next = pos->next;free(pos);pos = NULL;}
}

注:链表的方法还有很多,若仅考虑已有多个结点且增、删时都不影响头结点,或是空链表、仅有一个结点的链表等特殊情况,会使得方法并不完整。此文仅做示例。


http://www.ppmy.cn/ops/154802.html

相关文章

解锁FPGA的故障免疫密码

我们身处“碳基智能”大步迈向“硅基智能”序曲中,前者更像是后者的引导程序,AI平民化时代,万物皆摩尔定律。 越快越好,几乎适用绝大多数场景。 在通往人工智能的征程中,算力无处不在,芯片作用无可替代。 十六年前,就已宣称自己是一家软件公司的英伟达,现已登顶全球…

Elasticsearch:如何搜索含有复合词的语言

作者:来自 Elastic Peter Straer 复合词在文本分析和标记过程中给搜索引擎带来挑战,因为它们会掩盖词语成分之间的有意义的联系。连字分解器标记过滤器等工具可以通过解构复合词来帮助解决这些问题。 德语以其长复合词而闻名:Rindfleischetik…

集合的奇妙世界:Python集合的经典、避坑与实战

集合的奇妙世界:Python集合的经典、避坑与实战 内容简介 本系列文章是为 Python3 学习者精心设计的一套全面、实用的学习指南,旨在帮助读者从基础入门到项目实战,全面提升编程能力。文章结构由 5 个版块组成,内容层层递进&#x…

Fort Firewall:全方位守护网络安全

Fort Firewall是一款专为 Windows 操作系统设计的开源防火墙工具,旨在为用户提供全面的网络安全保护。它基于 Windows 过滤平台(WFP),能够与系统无缝集成,确保高效的网络流量管理和安全防护。该软件支持实时监控网络流…

【深度学习】常见模型-Transformer模型

Transformer 是一种深度学习模型,首次由 Vaswani 等人在 2017 年提出(论文《Attention is All You Need》),在自然语言处理(NLP)领域取得了革命性成果。它的核心思想是通过 自注意力机制(Self-A…

事务01之事务机制

事务机制 文章目录 事务机制一:ACID1:什么是ACID2:MySQL是如何实现ACID的 二:MySQL事务机制综述1:手动管理事务2:事务回滚点3:事务问题和隔离机制(面试)3.1:事…

消息队列篇--通信协议篇--WebSocket(WebSocket特点,HTTP升级到WebSocket,STOMP协议使用,通信类型分类,全双工通信等)

WebSocket通信是使用WebSocket的通信协议,它在2011年被IETF标准化为RFC 6455。WebSocket协议提供了一个在单个TCP连接上进行全双工通信的渠道,允许客户端和服务器之间保持持久连接,支持双向数据传输,使得服务器和客户端之间可以发…

Ubuntu 系统,如何使用双Titan V跑AI

要在Ubuntu系统中使用双NVIDIA Titan V GPU来运行人工智能任务,你需要确保几个关键组件正确安装和配置。以下是基本步骤: 安装Ubuntu操作系统: 下载最新版本的Ubuntu服务器或桌面版ISO文件。使用工具如Rufus(Windows)或…