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

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

目录

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

单链表介绍

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

在这里插入图片描述

链表的初始化

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;

相关文章:

  • 前后端分离项目部署,vue--nagix发布部署,.net--API发布部署。
  • TYPE-C接口PD取电快充协议芯片ECP5701:支持PD 2.0和PD 3.0(5V,9V,12V,15V,20V)
  • 【数据结构】探索排序的奥秘
  • HTML零基础自学笔记(上)-7.18
  • laravel框架基础通识-新手
  • 【计算机视觉】siamfc论文复现实现目标追踪
  • 基于 Electron+Vite+Vue3+Sass 框架搭建
  • Python爬虫(2) --爬取网页页面
  • HydraRPC: RPC in the CXL Era——论文阅读
  • 计算机视觉9 全卷积网络
  • 在 CentOS 7 上安装 Docker 并安装和部署 .NET Core 3.1
  • FPGA-计数器
  • 控制欲过强的Linux小进程
  • 【线性代数】矩阵变换
  • 使用Top进行设备性能分析思路
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • Android开发 - 掌握ConstraintLayout(四)创建基本约束
  • Centos6.8 使用rpm安装mysql5.7
  • CSS3 变换
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • iOS筛选菜单、分段选择器、导航栏、悬浮窗、转场动画、启动视频等源码
  • Java 最常见的 200+ 面试题:面试必备
  • java概述
  • LeetCode18.四数之和 JavaScript
  • python 学习笔记 - Queue Pipes,进程间通讯
  • React16时代,该用什么姿势写 React ?
  • Spark RDD学习: aggregate函数
  • TypeScript实现数据结构(一)栈,队列,链表
  • Vim 折腾记
  • 多线程 start 和 run 方法到底有什么区别?
  • 关于for循环的简单归纳
  • 京东美团研发面经
  • 普通函数和构造函数的区别
  • 前端学习笔记之原型——一张图说明`prototype`和`__proto__`的区别
  • 浅谈JavaScript的面向对象和它的封装、继承、多态
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 使用Gradle第一次构建Java程序
  • 微信如何实现自动跳转到用其他浏览器打开指定页面下载APP
  • 阿里云ACE认证学习知识点梳理
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • #pragma once
  • (3)llvm ir转换过程
  • (3)STL算法之搜索
  • (Qt) 默认QtWidget应用包含什么?
  • (不用互三)AI绘画工具应该如何选择
  • (二)PySpark3:SparkSQL编程
  • (附源码)springboot猪场管理系统 毕业设计 160901
  • (篇九)MySQL常用内置函数
  • (十) 初识 Docker file
  • (学习日记)2024.03.25:UCOSIII第二十二节:系统启动流程详解
  • .JPG图片,各种压缩率下的文件尺寸
  • .net dataexcel 脚本公式 函数源码
  • .net FrameWork简介,数组,枚举