【手撕数据结构】拿捏单链表

目录

  • 单链表介绍
  • 链表的初始化
  • 打印链表
  • 增加节点
    • 尾插
    • 头插
    • 再给定位置之后插入
    • 在给定位置之前插入
  • 删除节点
    • 尾删
    • 头删
    • 删除给定位置的节点
    • 删除给定位置之后的节点
  • 查找节点

单链表介绍

单链表也叫做无头单向非循环链表,链表也是一种线性结构。他在逻辑结构上一定连续,但是在物理结构上不一定连续(随机开辟空间),链表由一个或多个节点足够,每个节点由两部分组成,一个是存储的数据,一个是指向下一个节点的指针。

在这里插入图片描述

链表的初始化

typedef int SLTDataType;	//方便以后修改存储数据类型typedef struct SListNode
{SLTDataType val;struct SListNode* next;
}SLTNode;

打印链表

void SLTPrint(SLTNode* phead)
{assert(phead);SLTNode* pcur = phead;while (pcur){printf("%d->", pcur->val);pcur = pcur->next;}printf("NULL");
}

这里就直接依次打印元素即可,我们习惯不动头结点,所以创建一个变量存储头结点来遍历

增加节点

SLTNode* SLTBuyNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc failed");exit(1);}else{newnode->val = x;newnode->next = NULL;}return newnode;
}

因为我们每次插入不管是头插还是尾插,都要创建一个新的节点,我们不妨封装一个函数专用

尾插

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//链表和顺序表不一样不是每次2倍申请空间,而是来一个数据申请一个空间SLTNode* newnode = SLTBuyNode(x);if (*pphead == NULL){*pphead = newnode;}else{SLTNode* pcur = *pphead;/*while (pcur){pcur = pcur->next;}pcur = newnode;*/	//这种写法虽然找到了插入节点的位置,但是无法修改上一个节点的next指针while (pcur->next){pcur = pcur->next;	//这里就找到了插入节点的位置}pcur->next = newnode;//并且修改了上一个节点的指针}
}

这里要做的就是两点,找到旧的尾节点,将旧的尾节点指针指向新的尾节点,然后将新的尾节点插入;

注:新结点创建的时候指针域就已经置空,所以尾插时不需要再将新结点的指针域置空。还有就是如果只有一个节点,那么就可以直接插入

头插

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);/*if (*pphead == NULL){*pphead = newnode;}else{SLTNode* pcur = *pphead;*pphead = newnode;(*pphead)->next = pcur;}*///优化版本newnode->next = *pphead;	//这里就巧妙用了SLTBuyNode函数创建节点时候将next指针设置为NULL特性,//反正newonde是在一个固定的位置插入,不用像尾插一样遍历,而插入节点我们现在只需要改变next指针//在改变原来的头结点之前把新头结点的next指向旧头结点,再更新头结点*pphead = newnode;

这里可以看优化版本,可以不处理链表为空的情况,因为我们总是在头结点的位置插入,只需要把新头节点的指针指向旧头结点,然后取代旧头结点

再给定位置之后插入

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);SLTNode* Next = pos->next;if (Next){pos->next = newnode;newnode->next = Next;}else{pos->next = newnode;}//优化版本//assert(pos);//SLTNode* newnode = SLTBuyNode(x);//newnode->next = pos->next;//pos->next = newnode;		//这种也可以同时处理一个节点和多个节点情况
}

这里也建议使用优化版本,不用看考虑只有一个节点的情况,插入就是把新节点的指针指向原来插入位置之后的节点,然后把指定位置的nxet指针指向2新节点。

在给定位置之前插入

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);/*SLTNode* ret = SLTFind(pos,pos->val);assert(ret != NULL);*/assert(pos);	//外面用查找函数返回值即可SLTNode* newnode = SLTBuyNode(x);SLTNode* pcur = *pphead;if (pcur->next == NULL){SLTPushFront(pphead,x);}else{while (pcur->next != pos){pcur = pcur->next;}pcur->next = newnode;newnode->next = pos;}}

这里要注意的是,在给定位置之前插入数据,我们就要先找到给定位置之前的那个节点,将那个节点的next指针指向插入的节点,再将插入的节点next指针指向给定位置的节点即可。但是如果链表只有一个元素,那么我们永远找不到给定位置之前的节点,pcur->next!=pos恒成立,所以单独处理,一个节点之前插入,相当于头插,只需要调用头插函数即可

删除节点

尾删

void SLTPopBack(SLTNode** pphead)
{assert(pphead && *pphead);	//*pphead链表不能为空SLTNode* pcur = *pphead;if ((*pphead)->next == NULL)	//如果只有一个节点,则无需考虑next指针{free(*pphead);*pphead = NULL;}else{/*while (pcur->next){pcur = pcur->next;}free(pcur);pcur == NULL;*///上面不能写的原因是,虽然找到了尾节点并释放,但是前一个节点的next指针依然指向尾节点//导致下一次删除尾节点时,对一块释放的空间访问,这里把pucr尾节点设置NULL不代表上一个节点的值为NULL,他的值依然是这块被释放的空间while (pcur->next->next)//找到尾节点之前的节点{pcur = pcur->next;}SLTNode* del = pcur->next;		//因为循环只能找到尾节点之前的节点,释放尾节点之前,把尾节点存起来free(del);del = NULL;pcur->next = NULL;}
}

这里要注意,我们先找到尾节点并释放那块内存空间,但是上一个节点的next指针也要记得设置NULL,不然他依然指向这块被释放的空间.

头删

void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* del = *pphead;*pphead = (*pphead)->next;free(del);del = NULL;
}

头删与尾删的区别是不需要考虑被删除节点的next指针,因为单链表是向后遍历的,头删的next指针并不会影响新的链表的遍历,所以我们只需找到旧头节点的下一个节点作为新的头节点,把原来的头节点释放即可

删除给定位置的节点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);	//*pphead不为空链表assert(pos);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else if (*pphead == pos){*pphead = (*pphead)->next;free(pos);pos = NULL;}else{SLTNode* Next = pos->next;SLTNode* pcur = *pphead;while (pcur->next != pos){pcur = pcur->next;}free(pos);pos = NULL;pcur->next = Next;}//优化版本//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;//}}

要删除给定位置的节点之前要先判断这个节点是否在链表中存在(SLTFind)方法 提供pos参数,这里通常只需要找到给定位置之前的节点,然后将其的next指针指向给定位置之后的节点即可.但是有两种情况需要单独处理,第一种情况就是链表只有一个节点时,只需要直接头删即可,第二种情况就是链表有多个节点,我们要删除头结点,这样的我们永远找不到给定位置之前的节点,pcur->next != pos恒成立,这种情况也可以通过头删函数解决

删除给定位置之后的节点

void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);//pos->next = pos->next->next;		//free(pos->next);//pos->next = NULL;SLTNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}

注意:不能像注释那样写的原因:在这里插入图片描述

查找节点

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{assert(phead);SLTNode* pcur = phead;while (pcur){if (pcur->val == x){return pcur;}pcur = pcur->next;}return NULL;
}

只需要从头节点开始遍历查找即可,如果找到了就返回该节点,如果到NULL,返回null;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/382216.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

展望未来:利用【Python】结合【机器学习】强化数据处理能力

欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 文章目录 一、引言二、数据清洗与预处理三、特征工程四、数据可视化五、模型训练与评估六、模型部署与优化七、总结 在数据驱动的时代,数据处理与机器学习技术的结合已成为推动业务增长和创新的关键…

Redis 7.x 系列【25】集群部署

有道无术,术尚可求,有术无道,止于术。 本系列Redis 版本 7.2.5 源码地址:https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 概述2. 配置文件2.1 cluster-enabled2.2 cluster-config-file2.3 cluster-node-tim…

HAL库源码移植与使用之RTC时钟

实时时钟(Real Time Clock,RTC),本质是一个计数器,计数频率常为秒,专门用来记录时间。 普通定时器无法掉电运行!但RTC可由VBAT备用电源供电,断电不断时 这里讲F1系列的RTC 可以产生三个中断信号&#xff…

TYPE-C接口PD取电快充协议芯片ECP5701:支持PD 2.0和PD 3.0(5V,9V,12V,15V,20V)

随着智能设备的普及,快充技术成为了越来越多用户的刚需。而TYPE-C接口作为新一代的USB接口,具有正反插、传输速度快、充电体验好等优点,已经成为了快充技术的主要接口形式。而TYPE-C接口的PD(Power Delivery)取电快充协…

poi库简单使用(java如何实现动态替换模板Word内容)

目录 Blue留言: Blue的推荐: 什么是poi库? 实现动态替换 第一步:依赖 第二步:实现word模板中替换文字 模板word: 通过以下代码:(自己建一个类,随意取名&#xf…

[排序]hoare快速排序

今天我们继续来讲排序部分,顾名思义,快速排序是一种特别高效的排序方法,在C语言中qsort函数,底层便是用快排所实现的,快排适用于各个项目中,特别的实用,下面我们就由浅入深的全面刨析快速排序。…

JVM监控及诊断工具-命令行篇--jcmd命令介绍

JVM监控及诊断工具-命令行篇5-jcmd:多功能命令行 一 基本情况二 基本语法jcmd -ljcmd pid helpjcmd pid 具体命令 一 基本情况 在JDK 1.7以后,新增了一个命令行工具jcmd。它是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能…

简历网站分享

作者本人自己编写了一个简历站点,分享给大家。在线链接 , github仓库

从PyTorch官方的一篇教程说开去(3.3 - 贪心法)

您的进步和反馈是我最大的动力,小伙伴来个三连呗!共勉。 贪心法,可能是大家在处理陌生问题时候,最容易想到的办法了吧? 还记得小时候,国足请了位洋教练发表了一句到现在还被当成段子的话:“如…

【深入C++】map和set的使用

文章目录 C 中的容器分类1. 顺序容器2. 关联容器3. 无序容器4. 容器适配器5. 字符串容器6. 特殊容器 set1.构造函数2.迭代器3.容量相关的成员函数4.修改器类的成员函数5.容器相关操作的成员函数 multiset1.equal_range map1.初始化相关的函数2.迭代器3.容量相关的成员函数4.访问…

58. 不理解竞态问题

内容 竞态问题可能程序员面临的最困难和最隐蔽的错误之一。作为 Go 开发者,必须理解数据竞争和竞态条件等关键方面,包括它们可能产生的影响以及如何避免。接下来将首先讨论数据竞争与竞态条件的区别,然后研究 Go 内存模型及其重要性。 数据…

SpringBoot常用功能实现

1. 配置文件多环境配置 1.1 创建不同环境配置文件 文件名前缀和后缀为标准固定格式&#xff0c;不可以改变。 1.2 pom中加入文件配置 可以使用<activation>标签设置默认环境。 <profiles><profile><id>dev</id><activation><active…

Typora 1.5.8 版本安装下载教程 (轻量级 Markdown 编辑器),图文步骤详解,免费领取(软件可激活使用)

文章目录 软件介绍软件下载安装步骤激活步骤 软件介绍 Typora是一款基于Markdown语法的轻量级文本编辑器&#xff0c;它的主要目标是为用户提供一个简洁、高效的写作环境。以下是Typora的一些主要特点和功能&#xff1a; 实时预览&#xff1a;Typora支持实时预览功能&#xff0…

在 CentOS 7 上安装 Docker 并安装和部署 .NET Core 3.1

1. 安装 Docker 步骤 1.1&#xff1a;更新包索引并安装依赖包 先安装yum的扩展&#xff0c;yum-utils提供了一些额外的工具&#xff0c;这些工具可以执行比基本yum命令更复杂的任务 sudo yum install -y yum-utils sudo yum update -y #更新系统上已安装的所有软件包到最新…

【spring boot】初学者项目快速练手

项目视频&#xff1a;一小时带你从0到1实现一个SpringBoot项目开发_哔哩哔哩_bilibili 注解视频&#xff1a;10、Java高级技术&#xff1a;注解&#xff1a;认识注解_哔哩哔哩_bilibili 一、基础知识 1.注解Annotation &#xff08;1&#xff09;定义 注解是Java代码里的特…

Golang | Leetcode Golang题解之第257题二叉树的所有路径

题目&#xff1a; 题解&#xff1a; func binaryTreePaths(root *TreeNode) []string {paths : []string{}if root nil {return paths}nodeQueue : []*TreeNode{}pathQueue : []string{}nodeQueue append(nodeQueue, root)pathQueue append(pathQueue, strconv.Itoa(root.V…

干货-并发编程提高——线程切换基础(一)

现在的时分&#xff08;time-sharing&#xff09;多任务&#xff08;multi-task&#xff09;操作系统架构通常都是用所谓的“时间分片&#xff08;time quantum or time slice&#xff09;”方式进行抢占式&#xff08;preemptive&#xff09;轮转调度&#xff08;round-robin式…

HydraRPC: RPC in the CXL Era——论文阅读

ATC 2024 Paper CXL论文阅读笔记整理 问题 远程过程调用&#xff08;RPC&#xff09;是分布式系统中的一项基本技术&#xff0c;它允许函数在远程服务器上通过本地调用执行来促进网络通信&#xff0c;隐藏底层通信过程的复杂性简化了客户端/服务器交互[15]。RPC已成为数据中心…

【iOS】内存五大分区

目录 堆&#xff08;Heap&#xff09;是什么五大分区栈区堆区全局/静态区常量区&#xff08;即.rodata&#xff09;代码区&#xff08;.text&#xff09; 函数栈堆和栈的区别和联系图解 OC语言是C语言的超集&#xff0c;所以先了解C语言的内存模型的内存管理会有很大帮助。C语言…

PHP接入consul,注册服务和发现服务【学习笔记】

PHP接入consul,注册服务和发现服务 consul安装 链接: consul安装 启动consul C:\Users\14684>consul agent -dev安装TP5 composer create-project topthink/think5.0.* tp5_pro --prefer-dist配置consul 创建tp5_pro/application/service/Consul.php <?php /*****…