数据结构之 “单链表“

news2024/12/23 12:39:57

(1)在顺表表中,如果是头插/删的时间复杂度是O(1);尾插/删的时间复杂度是O(N)
(2)增容一般是呈2倍的增长,势必会有一定的空间浪费。比如:申请了50个空间,只用了两个?(链表可以解决空间浪费的问题)

这一章节的内容是关于单链表。

文章目录

  • 1. 链表
  • 2. 单链表
    • 1. 单链表的概念
    • 2. 单链表的实现
      • 2.1 尾插
      • 2.2 头插
      • 2.3 尾删
      • 2.4 头删
      • 2.5 查找
      • 2.3 特定位置(之前/之后)插入
      • 2.6删除特定位置pos处的结点
      • 2.7 删除pos之后的结点
      • 2.8 销毁链表

1. 链表

链表也是线性表的一种。我们仍然从物理结构和线性结构来分析
(1)物理结构(真实):不是线性
(2)线性结构(想象):线性

重点:链表是由一个一个的结点连接起来的。每次创建一个结点,不存在浪费的情况。
一个结点里面存储的是:数据+下一个结点的地址。

链表里的结点,它们的地址不是连续的,而是靠(存储的地址)连接起来的。
在这里插入图片描述

在这里插入图片描述

(3)在链表中,没有增容的概念。如果要增加数据,直接再申请一个结点大小的空间即可。

2. 单链表

1. 单链表的概念

单链表的全称是”不带头,单向,不循环链表“。

  1. 单链表的定义:在.h里

在这里插入图片描述
(1)创建链表—>在test.c里

这个方法只是示范一下,平常创建链表并不会像这么麻烦。
在初始情况下,链表是空链表,只有一个结点,指向NULL,之后尾插即可达到申请结点的结果。

//这个是写在test.c的内容

#include"SLTNode.h"
//创建链表
void creatListNode()
{
	//使用malloc记得写头文件stdlib
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	node1->data = 1;
	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	node2->data = 2;
	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	node3->data = 3;

	node1->next = node2;
	node2->next = node3;
	node3->next = NULL;
}

int main()
{
	creatListNode();
	return 0;
}

在这里插入图片描述
(2)打印链表出来看看
在这里插入图片描述

2. 单链表的实现

2.1 尾插

不管是头插还是尾插,都需要再申请一个结点大小的空间,所以可以将它封装为一个函数,之后调用即可。

尾插比较简单,有两种可能。

1.链表不为空。最后一个结点的next指向NULL,我们只需将 (最后一个结点的next) 指向 (想插入的结点的地址newnode) 即可

2.链表为空,就不用找结点了。在刚开始时,我们创建了链表struct SLTNode,这是空链表,只有一个头结点(phead)指向NULL,我们将phead->next指向newnode即可

注意在尾插时传过去的是地址,这样形参的改变可以改掉实参。

//SLTNode.h里的内容

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//定义链表的结点
typedef int SLTDataType;
typedef struct SLTNode
{
	SLTDataType data;
	struct SLTNode* next;
}SLTNode;

//申请新结点
SLTNode* SLTBuyNode(SLTDataType x);

//尾插
void SLTPushBack(SLTNode** pphead,SLTDataType x);


//打印链表
void SLTPrint(SLTNode* phead);

//SLTNode.c里面的内容

#include"SLTNode.h"
//用于打印链表的函数的定义
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d(地址:%p) -> ",
		    pcur->data, pcur->next);
		pcur = pcur->next;
	}
	printf("NULL");
}

//用于申请新结点的函数的定义
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	
	//判断一下是否申请成功
	if (node == NULL)
	{
		perror("malloc");
		return 1;
	}
	node->next = NULL;
	node->data = x;
	return node;
}

//尾插函数的定义         pphead是第一个结点指针的地址(地址的地址)
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	//先申请新结点
	SLTNode* newnode = SLTBuyNode(x);

	//链表为空
	if (*pphead == NULL)        //*pphead是第一个结点的指针
	{
		*pphead = newnode;
	}
	else  //链表不为空
	{
	  //接下来将尾结点->next指向newnode
	  //找尾结点不能用phead直接遍历找到尾结点,因为这样的话就找不到第一个结点了(单链表只能往后)我们需要重新申请一个来存放第一个结点的地址)
		SLTNode* pcur = *pphead;
		while (pcur->next)         //不为NULL时可进入循环
		{
			pcur = pcur->next;     //将指针pcur里存放成下一个结点的地址
		}
		//出循环表示pcur是尾结点地址,将它的next修改
		pcur->next = newnode;
	}
	

}  
//test.c的内容

#include"SLTNode.h"
void SLTtest01()
{
	SLTNode* plist = NULL;

	SLTPushBack(&plist, 1);
	SLTPrint(plist);

	SLTPushBack(&plist, 2);
	SLTPrint(plist);


	SLTPushBack(&plist, 3);
	SLTPrint(plist);
}

int main()
{
	SLTtest01();
	return 0;
}

2.2 头插

1.头插仍然是将pphead(地址)传过去
2.头插是将申请的结点的next指向第一个结点的地址。即 newnode->next =* pphead
3.记得最后将*pphead移到新结点处

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);   //已经头插了,那传过来的参数指定不能为空
	SLTNode* newnode=SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;

}

2.3 尾删

尾删:链表不可以为空。

在尾删时,不能直接将最后一个结点释放再置为空,因为我们还需要找到倒数第二个结点,将它的next改为NULL。

还有可能遇见只有一个结点的情况,我们直接把它释放置为空即可。

方法:
(1)创建一个ptail,遍历,使之成为倒数第二个结点,即ptail->next->next=NULL;,将ptail->next指向空。再将ptail往后走成为最后一个结点,将其释放。

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);  //传过来的参数不能为空,链表不能为空
	if((*pphead)->next==NULL)
	{
	    free(*pphead);
	    *pphead=NULL;
	 }
	 else{
	 	SLTNode* ptail = *pphead;
	    while (ptail->next->next)
	    {
		    ptail = ptail->next;
	    }  
	    ptail->next = NULL;
	    ptail = ptail->next;
	    free(ptail);
	    ptail = NULL;
     } 
}

(2)将prev一直是ptail的前一个

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);  //传过来的参数不能为空,链表不能为空
	if((*pphead)->next==NULL)
	{
	    free(*pphead);
	    *pphead=NULL;
	 }
	else{
	   SLTNode* ptail = *pphead;
	   SLTNode* prev = NULL;
	   while (ptail->next)
	   {
		   prev = ptail;            //第一次时,prev=*pphead
		   ptail = ptail->next;     //第一次循环时,ptail=第二个结点的指针
	   }                            //当ptail->next=NULL时,ptail最后一个,prev是倒数第二个
	   prev->next = NULL;
	   free(ptail);
	   ptail = NULL;
   }
}

2.4 头删

头删同样需要断言。

要是删除第一个结点,那么第二个结点等一下就找不到了,我们应该先将第二个结点存起来。再将*pphead释放,再将 * pphead指向第二个结点

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

2.5 查找

不用传地址过去,并不希望在查找时不小心将内容修改

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没有找到
	return NULL;
}

在这里插入图片描述

2.3 特定位置(之前/之后)插入

  1. 在特定位置之前插入,那插入这个数据会影响谁呢?(需要第一个结点)

在这里插入图片描述
由图可知:prev->next 将会被影响。

但是如何可以找到prev呢?单链表只能从前往后找,并不能从pos往前找。

我们可以采用循环,直到 xxxx->next == pos为止。当满足这个条件时,xxxx就是prev。

注意:在插入时,链表phead可以为空,但参数pphead不能为空。pos也不能为空

prev->next = newnode;
newnode->next = pos;

//SLTNode.h里的内容
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
//SLTNode.c里的内容
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);


	//如果pos是第一个结点,那么这就变成头插了
	if (pos == *pphead)
	{
		SLTPushFront(*pphead, x);
	}

	else
	{
		SLTNode* newnode = SLTBuyNode(x);  //新结点
		SLTNode* prev = *pphead;           //先让prev是第一个结点的指针
		while (prev->next!=pos)            //循环让prev=pos前一个结点指针
		{
			prev = prev->next;
		}
		prev->next = newnode;             //让prev的下一个是新结点
		newnode->next = pos;
	}
}
//test.c里的内容

//通过x找到pos
SLTNode* find = SLTFind(plist, 2);
SLTInsert(&plist, find, 9);
  1. 在特定位置之后插入(不需要第一个结点)
    在这里插入图片描述

在“特定位置之后插入”的函数的参数中,并没有第一个结点的地址,为什么呢?

我们已经知道了pos这个地址,可以直接找到它的下一个结点的地址,并不需要通过头结点一个一个往后找。

void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead&&pos);
	//如果是空链表
	if (*pphead == NULL)
	{
		SLTPushBack(*pphead, x);
	}
	//不是空链表
	else
	{
		SLTNode* newnode = SLTBuyNode(x);  //新结点
		SLTNode* Next = pos->next;  //pos的下一个结点
		pos->next = newnode;
		newnode->next = Next;
	}
}

2.6删除特定位置pos处的结点

需要修改pos前一个结点 (prev) 的next
在这里插入图片描述

//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);

	//头删
	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;
	}
}

2.7 删除pos之后的结点

//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);

	//pos pos->next pos->next->next
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

2.8 销毁链表

//销毁链表
void SListDestroy(SLTNode** pphead)
{
	assert(pphead && *pphead);

	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

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

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

相关文章

【Matlab】SSA-BP麻雀搜索算法优化BP神经网络回归预测 可预测未来(附代码)

资源下载&#xff1a; 资源合集&#xff1a; 目录 一&#xff0c;概述 传统的BP神经网络存在一些问题&#xff0c;比如容易陷入局部最优解、训练速度慢等。为了解决这些问题&#xff0c;我们引入了麻雀算法作为优化方法&#xff0c;将其与BP神经网络相结合&#xff0c;提出了…

精准高效,省时省力——2024年翻译工具新趋势

如果你收到一份外文文档&#xff0c;但是你看不懂急需翻译成中文&#xff0c;将文档内容复制粘贴到翻译的的窗口获得中文内容是不是很麻烦。我了解到了deepl翻译文档之后还认识了不少的翻译神器&#xff0c;这次分享给你一起试试吧。 第一款福晰在线翻译 链接直达>>htt…

3D工艺大师:精准助力医疗设备远程维修

医疗设备是现代医疗体系中不可或缺的重要工具&#xff0c;它们帮助医生更准确地诊断病情&#xff0c;更有效地进行治疗。但随着技术的进步&#xff0c;这些设备变得越来越复杂&#xff0c;维修起来也面临诸多挑战。 首先&#xff0c;医疗设备结构复杂&#xff0c;零件众多&…

【Material-UI】Rating组件中的Rating precision属性

文章目录 一、Rating组件概述1. 组件简介2. precision属性的作用 二、Rating precision的基本用法三、Rating precision属性详解1. 精度选择的意义2. 如何在项目中选择合适的精度 四、Rating precision属性的实际应用场景1. 电商平台中的应用2. 电影评分应用3. 专业评测网站 五…

[Tomcat源码解析]——热部署和热加载原理

热部署 在Tomcat中可以通过Host标签设置热部署,当 autoDeploy为true时,在运行中的Tomcat中丢入一个war包,那么Tomcat不需要重启就可以自动加载该war包。 <Host name="localhost" appBase="webapps"unpackWARs="true" autoDeploy="…

Ubuntu18.04 下安装CUDA

安装步骤 1.查看是否安装了cuda # 法1 cat /usr/local/cuda/version.txt # 法2 nvcc --version 2.若没有安装&#xff0c;则查看是否有N卡驱动&#xff0c;若无N卡驱动&#xff0c;则到软件与更新 -> 附加驱动中安装驱动 3.查看N卡驱动支持的cuda版本 nvidia-smi 如下…

RabbitMQ 集群与高可用性

目录 单节点与集群部署 1.1. 单节点部署 1.2. 集群部署 镜像队列 1.定义与工作原理 2. 配置镜像队列 3.应用场景 4. 优缺点 5. Java 示例 分布式部署 1. 分布式部署的主要目标 2. 典型架构设计 3. RabbitMQ 分布式部署的关键技术 4. 部署策略和实践 5. 分布式部署…

图像变换——等距变换、相似变换、仿射变换、投影变换

%%图像变换 % I imread(cameraman.tif); I imread(F:\stitching\imagess\or\baiyun2.jpg); figure; imshow(I); title(原始图像); [w,h]size(I); thetapi/4;%旋转角 t[200,80];%平移tx,ty s0.3;%缩放尺度 %% 等距变换平移变换旋转变换 H_eprojective2d([cos(theta) sin(theta…

体育风尚杂志体育风尚杂志社体育风尚编辑部2024年第8期目录

体讯 体育产业“破圈” 3-7 成都大运会获2023年度最佳体育赛事媒体设施奖 8-10 斯巴达勇士赛 2024斯巴达勇士赛深圳站漫威主题赛在深圳光明欢乐田园揭幕 11-15 篮球 CBA季后赛 深圳马可波罗挺进季后赛八强 16-24 游泳 周六福2024年全国游泳冠军赛4月深圳激…

集成电路与电路基础之-二极管

二极管是什么 二极管&#xff0c;又称肖特基二极管或晶体二极管&#xff0c;是一种最基本的半导体器件之一。它由半导体材料&#xff08;如硅、硒、锗等&#xff09;制成&#xff0c;其内部结构是一个PN结&#xff0c;即由一个P型半导体区和一个N型半导体区组成。这种结构赋予…

【freeDiameter】服务端和客户端的连接流程

连接流程详解 进程启动时&#xff0c;先使用main_cmdline解析命令行参数&#xff0c;比如使用-c就会使用指定路径的配置文件&#xff0c;使用-d就会启用后台进程。 之后使用fd_core_initialize初始化核心库。具体会先使用fd_conf_init初始化配置&#xff0c;比如设置各项的默…

Android活动(activity)与服务(service)进行通信

文章目录 Android活动&#xff08;activity&#xff09;与服务&#xff08;service&#xff09;进行通信活动与服务进行通信服务的生命周期 Android活动&#xff08;activity&#xff09;与服务&#xff08;service&#xff09;进行通信 活动与服务进行通信 上一小节中我们学…

国产3A大作《黑神话:悟空》,各类MOD+修改器+皮肤等资源大合集(附安装教程)

《黑色神话&#xff1a;悟空》的引擎让你可以修改角色的伤害、防御和各种物资数量&#xff0c;大大降低了游戏的难度&#xff0c;让动作游戏的玩家更容易享受到体验。但是&#xff0c;请注意&#xff0c;使用作弊引擎会大大降低游戏体验&#xff0c;因此请明智地使用它&#xf…

各种探针卡介绍

探针卡(Probe Card)是一种在半导体测试过程中至关重要的设备,主要用于晶圆测试阶段,通过探针与芯片上的焊垫或凸块直接接触,完成测试信号的传输和反馈。探针卡的种类多样,各有其特点和应用场景。以下是几种常见的探针卡类型详细介绍: 1. 刀片探针卡(Blade Probe Card)…

<Rust>egui学习之小部件(五):如何在窗口中添加图像部件?

前言 本专栏是关于Rust的GUI库egui的部件讲解及应用实例分析&#xff0c;主要讲解egui的源代码、部件属性、如何应用。 环境配置 系统&#xff1a;windows 平台&#xff1a;visual studio code 语言&#xff1a;rust 库&#xff1a;egui、eframe 概述 本文是本专栏的第五篇博…

【Qt】垂直布局管理器QVBoxLayout

垂直布局管理器QVBoxLayout 在之前学习Qt的过程中&#xff0c;将控件放在界面上&#xff0c;都是依靠“手动”的方式来布局的&#xff0c;但是手动调整的方式是不科学的。 手动布局的方式非常复杂&#xff0c;而且不精确无法对窗口大小进行自适应 因此Qt引入布局管理器来解决…

2-79 基于matlab的卷积稀疏的形态成分分析的医学图像融合

基于matlab的卷积稀疏的形态成分分析的医学图像融合&#xff0c;基于卷积稀疏性的形态分量分析 (CS-MCA) 的稀疏表示 (SR) 模型&#xff0c;用于像素级医学图像融合。通过 CS-MCA 模型使用预先学习的字典获得其卡通和纹理组件的 CSR。然后&#xff0c;合并所有源图像的稀疏系数…

13-springcloud gateway集成nacos实现负载均衡

网关作为访问系统的入口&#xff0c;负载均衡是必选项而不是可选项&#xff0c;本文介绍gateway与nacos集成&#xff0c;实现负载均衡的过程。关于springcloud gateway的基本用法&#xff0c;同学可以看看上篇文章: 12-使用gateway作为网关。 0、环境 jdk&#xff1a;1.8spri…

【GIT】idea中实用的git操作,撤回commit,撤回push、暂存区使用

IDEA中最常见的UI操作&#xff1a;【GIT】Idea中的git命令使用-全网最新详细&#xff08;包括现象含义&#xff09; 文章目录 问题一&#xff1a; idea撤回仅commit错误的代码&#xff08;仅本地仓库&#xff0c;因为还没推送到远程&#xff09;问题二&#xff1a; idea撤回Com…

【提示学习论文】CoCoLe:Conceptual Codebook Learning for Vision-Language Models

Conceptual Codebook Learning for Vision-Language Models&#xff08;ECCV 2024&#xff09; CPL的改进暂无代码 CPL 详见CPL论文 CoCoLe a&#xff1a;手工概念缓存的建立过程b&#xff1a;制作提示的过程&#xff0c;将图像输入Ev&#xff0c;得到image features v 作…