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

news2024/9/22 7:23:45

目录

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

单链表介绍

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

在这里插入图片描述

链表的初始化

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.coloradmin.cn/o/1938808.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

昇思25天学习打卡营第10天 | FCN图像语义分割

学习心得:全卷积网络(FCN)在图像语义分割中的应用 图像语义分割作为计算机视觉领域的一个重要分支,对于理解图像内容提供了非常关键的技术支持。通过学习并实践全卷积网络(FCN)在图像语义分割的应用&#…

2024-07-19 Unity插件 Odin Inspector9 —— Validation Attributes

文章目录 1 说明2 验证特性2.1 AssetsOnly / SceneObjectsOnly2.2 ChildGameObjectsOnly2.3 DisallowModificationsIn2.4 FilePath2.5 FolderPath2.6 MaxValue / MinValue2.7 MinMaxSlider2.8 PropertyRange2.9 Required2.10 RequiredIn2.11 RequiredListLength2.12 ValidateIn…

【学习笔记】无人机系统(UAS)的连接、识别和跟踪(八)-无人机探测与避让(DAA)机制

目录 引言 5.6 探测与避让(DAA)机制 5.6.1 基于PC5的探测与避让(DAA)机制 引言 3GPP TS 23.256 技术规范,主要定义了3GPP系统对无人机(UAV)的连接性、身份识别、跟踪及A2X(Airc…

HP ilo4服务器硬件监控指标解读

随着企业IT架构的复杂化,服务器的稳定性和可靠性成为保障业务连续性的关键因素。HP ilo4作为HP服务器的一个重要组件,提供了强大的远程管理和监控功能。本文将对使用监控易软件通过HP ilo4进行服务器硬件监控的指标进行解读,帮助运维团队更好…

数学建模-----SPSS参数检验和非参数检验

目录 1.参数检验 1.1独立样本t检验案例分析 1.1.1查看数据编号 1.1.2确定变量所属类型 1.1.3选项里面的置信区间 1.1.4对于结果进行分析 1.2配对样本t检验案例分析 1.2.1相关设置 1.2.2分析结果 2.非参数检验 2.1对比分析 2.2非参数检验的方法 2.3案例分析 2.3.1相…

Codeforces Round 960 (Div. 2)(A~C)题

A. Submission Bait 思路: 如果最大值有奇数个显然Alice赢&#xff0c;否则只需要看排序后是否存在n−i1是否为奇数且ai>ai−1即可。 代码: #include<bits/stdc.h> using namespace std; #define N 2000010 typedef long long ll; typedef unsigned long long ull; …

KMeans等其他聚类算法

KMeans算法是一种经典的聚类方法&#xff0c;最早由Stuart Lloyd在1957年提出&#xff0c;并在1982年由J. MacQueen推广和普及。虽然KMeans已经有几十年的历史&#xff0c;但它依然是数据挖掘和机器学习领域中最常用的聚类算法之一。 数学原理 KMeans算法的目标是将数据集分成…

Blender中的重拓扑修改器如何使用?

许多人还不了解Blender中的重拓扑编辑器及其使用方法。Blender中的重拓扑修改器提供了一系列工具和选项&#xff0c;以简化创建优化网格的过程&#xff0c;无论是出于何种目的&#xff0c;都能为3D艺术家和建模者节省大量时间和精力。那么&#xff0c;在Blender中重拓扑的定义是…

springcloud-config客户端启用服务发现报错找不到bean EurekaHttpClient

背景 在对已有项目进行改造的时候&#xff0c;集成SpringConfigStarter&#xff0c;编写完bootstrap.yml&#xff0c;在idea 启动项中编辑并新增VM options -Dspring.cloud.config.discovery.enabledtrue&#xff0c;该版本不加spring不会从configService获取信息&#xff0c;…

网络结构-组件-AI(九)

深度学习网络组件 RNN公式讲解计算示意图讲解 CNN计算示意 Normalization(归一化层)Normalization常见两种方式 Dropout层 RNN 循环神经网络&#xff08;recurrent neural network&#xff09; 主要思想&#xff1a; 即将整个序列划分成多个时间步&#xff0c;将每一个时间步的…

OrangePi AIpro 快速上手初体验

开发板开箱 1.1.包装 1.2.开发版 Orange Pi AIpro Orange Pi官网-香橙派&#xff08;Orange Pi&#xff09;开发板 1.3.引脚分布 1.4开发板资源简介 1CPU配备了4核64位ARM处理器&#xff0c;其中默认预留1个给AI处理器使用NPU集成了华为昇腾310BAI处理器&#xff0c;拥有4TF…

(二)原生js案例之数码时钟计时

原生js实现的数字时间上下切换显示时间的效果&#xff0c;有参考相关设计&#xff0c;思路比较难&#xff0c;代码其实很简单 效果 代码实现 必要的样式 <style>* {padding: 0;margin: 0;}.content{/* text-align: center; */display: flex;align-items: center;justif…

华为OD机试(C卷,200分)- 二叉树计算

题目描述 给出一个二叉树如下图所示&#xff1a; 请由该二叉树生成一个新的二叉树&#xff0c;它满足其树中的每个节点将包含原始树中的左子树和右子树的和。 左子树表示该节点左侧叶子节点为根节点的一颗新树&#xff1b;右子树表示该节点右侧叶子节点为根节点的一颗新树。…

笔记:Few-Shot Learning小样本分类问题 + 孪生网络 + 预训练与微调

内容摘自王老师的B站视频&#xff0c;大家还是尽量去看视频&#xff0c;老师讲的特别好&#xff0c;不到一小时的时间就缕清了小样本学习的基础知识点~Few-Shot Learning (1/3): 基本概念_哔哩哔哩_bilibili Few-Shot Learning&#xff08;小样本分类&#xff09; 假设现在每类…

【Linux】基础I/O——动静态库的制作

我想把我写的头文件和源文件给别人用 1.把源代码直接给他2.把我们的源代码想办法打包为库 1.制作静态库 1.1.制作静态库的过程 我们先看看怎么制作静态库的&#xff01; makefile 所谓制作静态库 需要将所有的.c源文件都编译为(.o)目标文件。使用ar指令将所有目标文件打包…

Linux应用——网络基础

一、网络结构模型 1.1C/S结构 C/S结构——服务器与客户机&#xff1b; CS结构通常采用两层结构&#xff0c;服务器负责数据的管理&#xff0c;客户机负责完成与用户的交互任务。客户机是因特网上访问别人信息的机器&#xff0c;服务器则是提供信息供人访问的计算机。 例如&…

[2019红帽杯]Snake

[2019红帽杯]Snake-CSDN博客 unity的题 下载下来看看是什么类型就是 这道题就是贪吃蛇 unity无脑找Assembly 用dnspy打开 一般就在这里慢慢找 但是你可以发现没有任何的信息 这里外接库 只能从这里下手试试 64位链接库的意思 游戏题,win!很关键 进入了Gameobject 看a1,小…

复现Android中GridView的bug并解决

几年前的一个bug&#xff0c;GridView的item高度不一致。如下图&#xff1a; 复现bug的代码&#xff1a; import android.os.Bundle; import android.widget.BaseAdapter; import android.widget.GridView; import androidx.appcompat.app.AppCompatActivity; import java.uti…

【Day12】登录认证、异常处理

1 登录 先创建一个新的 controller 层&#xff1a;LoginController RestController public class LoginController {Autowiredprivate EmpService empService;// 注入PostMapping("/login")public Result login(RequestBody Emp emp) { // 包装对象Emp e empServic…

html 单页面引用vue3和element-plus

引入方式&#xff1a; element-plus基于vue3.0&#xff0c;所以必须导入vue3.0的js文件&#xff0c;然后再导入element-plus自身所需的js以及css文件&#xff0c;导入文件有两种方法&#xff1a;外部引用、下载本地使用 通过外部引用ElementPlus的css和js文件 以及Vue3.0文件 …