数据结构--->单链表

news2024/11/27 10:23:23

文章目录

  • 链表
    • 链表的分类
  • 单链表
    • 单链表的存储结构
    • 单链表主要实现的接口函数
    • 单链表尾插
    • 动态申请新节点
    • 单链表头插
    • 单链表的尾删
    • 单链表的头删
    • 在指定位置之前插入
      • 单链表查找
      • 插入
    • 在指定位置之后插
    • 删除指定位置元素
    • 删除指定位置之后的元素
    • 顺序输出链表
    • 销毁单链表
  • 顺序表和单链表的区别
  • 关于指针传参

链表

链表是一种物理结构(储存结构)上不一定连续,不一定是顺序的存储结构,数据元素是通过链表中的指针链接次序实现的

链表的分类

image.png
链表有很多种类 两两匹配就一共有八种 这里主要介绍一下单链表(单向不带头不循环)

单链表

单链表的存储结构

图示

image.png
链表中的结点一般都是在堆上申请的,从堆上申请的空间,按照一定的规则申请的,两次申请的空间也能相同也可能不相同。用一个指针就能找到下一个结点的空间地址了,从而形成线性关系

typedef int ElemType;
typedef struct SListNode
{
	ElemType data;
	struct SListNode* next;
}SLTNode;

typedef SLTNode* LinkList;//定义链表

定义一个数据域和指针域。数据域用来存放数据,指针域的指针指向下一个结点的空间地址

单链表主要实现的接口函数

//创建新结点
SLTNode* NewSLTNode(ElemType x);
//尾插
void SLTPushBack(SLTNode** phead, ElemType x);
//头插
void SLTPushFront(SLTNode** phead, ElemType x);
//尾删
void SLTPopBack(SLTNode** phead);
//头删
void SLTPopFront(SLTNode** phead);
//单链表查找
SLTNode* SLTNodeFind(SLTNode* phead, ElemType x);
//在pos之前插入
void SLTInsert(SLTNode** phead, SLTNode* pos, ElemType x);
//在pos之后插入
void SLTInsertAfter(SLTNode* pos, ElemType x);
//删除pos位置
void SLTErase(SLTNode** phead, SLTNode* pos);
//删除pos位置后得
void SLTEraseAfter(SLTNode* pos);
//打印
void SLTNodePrintf(SLTNode* ps);

单链表尾插

单链表插入主要分为两种情况

  • 没有结点,单链表是空的情况

image.png

  • 有一个以上的结点

image.png

注意:
这里需要一个头指针(pehad 指向第一个结点的指针)来维护这个链表。否则将无法寻找到这个链表

void SLTPushBack(SLTNode** phead, ElemType x)
{
	//申请结点
	SLTNode* newnode = NewSLTNode(x);
	//空链表
	if (*phead == NULL)
	{
		*phead = newnode;
	}
	//有一个以上的结点
	else
	{
		SLTNode* tail = *phead;
		//遍历找最后一个结点
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		//连接新结点
		tail->next = newnode;
	}
}

链表结点的类型是struct SListNode* (结构体指针)类型,插入一个新元素,需要改变头指针的指向,所以实参需要传其地址,形参需要一个结构体指针的指针才可接受这个地址即二级指针
每次进行插入操作时都要申请结点,封装成函数,方便复用

动态申请新节点

SLTNode* NewSLTNode(ElemType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if(newnode == NULL)
	{
		perror("malloc fail");
		eixt(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

单链表头插

头插可以只看作一种情况
空和非空的处理结果都一样

图解
image.png
代码实现

void SLTPushFront(SLTNode** phead, ElemType x)
{
	//申请结点
	SLTNode* newnode = NewSLTNode(x);
	//空和非空链表都可处理
	newnode->next = *phead;
	*phead = newnode;
}

单链表的尾删

尾删要注意三种情况,分别是

  • 空链表
    错误处理
  • 只有一个结点
    直接释放该结点即可

image.png

  • 有两个结点以上的链表
    先找到最后一个结点,记录最后一个结点的前一个 然后释放最后一个结点,再将最后一个的前一个指针域置为NULL
    image.png

代码实现

void SLTPopBack(SLTNode** phead)
{
	//空链表
	assert(*phead);
	//只有一个结点
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//有两个结点以上的链表
	else
	{
		SLTNode* tail = *phead;
		SLTNode* tailprev = NULL;//记录最后一个的前一个
		while (tail->next != NULL)
		{
			tailprev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		tailprev->next = NULL;
	}
}

单链表的头删

头删时要注意两种情况分别是

  • 空链表
    错误处理
  • 有一个或多个结点
    先将头指针移动到第二个结点(只有一个结点第二个结点即为NULL也符合逻辑)的位置,在释放该结点
    image.png

image.png
代码实现

void SLTPopFront(SLTNode** phead)
{
	//空
	assert(*phead);
	//一个和多个结点处理逻辑一样
	SLTNode* newhead = (*phead)->next;
	free(*phead);
	*phead = newhead;
}

在指定位置之前插入

位置由自己指定
比如链表元素 1 2 3 4 在2的位置之前插入6 链表变为1 6 2 3 4
插入之前首先要找到该元素结点的位置

单链表查找

SLTNode* SLTNodeFind(SLTNode* phead, ElemType x)
{
	assert(phead);
	SLTNode* pos = phead;
	while (pos)
	{
		if (pos->data == x)
		{
			return pos;
		}
		pos = pos->next;
	}
	//没有该元素
	return NULL;
}

然后根据查找到元素的结点位置进行插入

插入

在指定位置插入时要考虑以下情况

  • 空链表
    不需要做处理 因为空链表找不到指定的位置
  • 指定位置不存在
    错误处理
  • 指定的位置是第一个结点
    复用头插即可
  • 其他情况下插入
    申请新节点 找到pos的前一个结点 将新结点连接接起来
    image.png
void SLTInsert(SLTNode** phead, SLTNode* pos, ElemType x)
{
	assert(*phead);
	assert(pos);
	
	if (pos == *phead)
	{
		SLTPushFront(phead, x);
	}
	else
	{
		//申请结点
		SLTNode* newnode = NewSLTNode(x);
		//找pos的前一个
		SLTNode* cur = *phead;
		SLTNode* posprev = NULL;
		while (cur != pos)
		{
			posprev = cur;
			cur = cur->next;
		}
		posprev->next = newnode;
		newnode->next = pos;
	}
}

在指定位置之后插

比如链表元素 1 2 3 4 在2的位置之后插入6 链表变为1 2 6 3 4
和指定位置之前插入一样,首先要找到该元素结点的位置在进行插入
在指定位置后插入要考虑以下情况

  • 空链表
    不需要做处理 因为空链表找不到指定的位置
  • 指定位置不存在
    错误处理
  • 其他情况下插入

image.png
这里不用考虑插入的位置是最后一个结点的位置,这样首先要遍历链表进行判断,在复用尾插,代价太大。

代码实现

void SLTInsertAfter( SLTNode* pos, ElemType x)
{
	assert(pos);
	//申请新结点
	SLTNode* newnode = NewSLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

删除指定位置元素

删除指定位置和插入指定位置一样,需要先查找到该元素结点的位置
比如链表元素 1 2 3 4 删除2的位置链表变为 1 3 4
删除pos位置要考虑以下情况

  • 空链表
    不需要做处理 因为空链表找不到指定的位置
  • 指定位置不存在
    错误处理
  • 指定位置是第一个结点
    复用头删
  • 其他情况下删除指定位置

image.png
代码实现

void SLTErase(SLTNode** phead, SLTNode* pos)
{
       //空链表
	assert(*phead);
	// 指定位置不存在
	assert(pos); 
	//复用头删
	if (pos == *phead)
	{
		SLTPopFront(phead);
	}
	else
	{
		//找pos前一个
		SLTNode* cur = *phead;
		SLTNode* prevpos = NULL;
		while (cur != pos)
		{
			prevpos = cur;
			cur = cur->next;
		}
		prevpos->next = pos->next;
		free(pos);
	}
}

删除指定位置之后的元素

比如链表元素 1 2 3 4 删除2之后位置 链表变为 1 2 4
删除指定位置之后的元素分别要考虑以下情况

  • 空链表
    不需要做处理 因为空链表找不到指定的位置
  • 指定位置不存在
    错误处理
  • 是否是尾结点
    错误处理
  • 其他情况下删除指定位置之后

image.png
代码实现

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

顺序输出链表

void SLTNodePrintf(SLTNode* phead)
{
	SLTNode* tail = phead;
	while (tail != NULL)
	{
		printf("%d " , tail->data);
		tail = tail->next;
	}
	printf("\n");
}

销毁单链表

void SLTNodeDestory(SLTNode** phead)
{
	assert(*phead);
	SLTNode* cur = *phead;
	SLTNode* curnext = NULL;
	while (cur != NULL)
	{
		curnext = cur->next;
		free(cur);
		cur = curnext;
	}
}

顺序表和单链表的区别

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,物理上不一定连续
随机访问O(1)O(n)
任意位置插入或删除可能需要挪动数据,效率太低O(n)只需要修改指针指向即可
插入元素动态顺序表,空间不够时需要扩容没有容量概念,用多少申请多少
应用场景元素高效存储+频繁访问频繁在任意位置插入和删除

关于指针传参

当你传递一个参数给函数的时候,这个参数会不会在函数内被改动决定了函数参数形式

  • 如果需要改动,则需要传指向这个参数的指针
    比如单链表的头插,尾插、头删等,都需要改变头指针的指向位置,也就是这个参数需要被改动,那么传这个参数的指针
  • 如果不用被改动,可以直接传递这个参数
    比如单链表中的查找和打印,直接传参数就可以了,查找和打印,不用修改里面的内容

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

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

相关文章

excel单元格内换行按什么快捷键

如果我们使用excel软件的时候,因为一些日常的操作太过繁琐想要简化自己的操作步骤的话,其实是有很多快捷方式在其中的。那么对excel单元格内换行按什么快捷键这个问题,据小编所知我们可以在表格中使用Alt Enter来进行换行。详细内容就来看下…

【云备份】数据管理模块

文章目录 1. 数据管理模块要管理什么数据?2. 数据管理模块如何管理数据?3. 数据管理模块的具体实现BackupInfo 数据信息类NewBackupInfo —— 获取各项属性信息 DataManager 数据管理类构造函数析构函数insert —— 新增update —— 修改GetOneByURL——…

C语言——标识符

一、标识符是什么 标识符是C程序的最基本组成部分,例如:变量名称、函数名称、数据类型等等,都是一个标识符。标识符的要求是:必须由字母(区分大小写)、数字、下划线组成。而且,标识符的第一个字…

JDBC编程基础

JDBC编程基础 JDBC介绍创建JDBC项目的步骤1.引入依赖2.注册驱动3.获取数据库连接4.获取sql执行对象 JDBC 常用 API 详解sql执行对象PreparedStatement作用 事务管理结果集对象 JDBC项目demo测试 JDBC介绍 每个数据库都会提供一组API来支持程序员实现自己客户端,自己…

SQL Server:流程控制语言详解

文章目录 一、批处理、脚本和变量局部变量和全局变量1、局部变量2、全局变量 二、顺序、分支和循环结构语句1、程序注释语句2、BEGIN┅END语句块3、IF┅ELSE语句4、CASE语句5、WHILE语句6、BREAK和CONTINUE语句BREAK语句CONTINUE语句 三、程序返回、屏幕显示等语句1、RETURN语句…

通义灵码,你的智能编码助手,免费公测啦!

目录 ​编辑 1、介绍 2、安装 3、功能介绍 行/函数级实时续写 自然语言生成代码 单元测试生成 代码注释生成 代码解释 研发智能问答 多编程语言、多编辑器全方位支持 4、视频 🍃作者介绍:双非本科大三网络工程专业在读,阿里云专家…

TopK问题(用堆解决)

我们继续来延续我们上面的TopK问题,TopK问题一般是在解决有很多数的情况下,我们的k是个和小的值,然后我们是要找到最小或者最大的K个数,这类问题我们也称之为TopK问题,面对这种的问题,如果数字不是很大的情…

java--子类中访问其他成员的特点

1.在子类方法中访问其他成员(成员变量、成员方法),是依照就近原则的。 ①先子类局部范围找。 ②然后子类成员范围找。 ③然后父类成员范围找,如果父类范围还没有找到则报错。 2.如果父类中,出现了重名的成员,会优先使用子类的…

linux 磁盘管理、分区管理常用命令

文章目录 基础命令挂载新硬盘/分区添加内存交换分区swaplvm分区管理模式 基础命令 查看目录文件大小 du -sh /backup du -sh /backup/* du -sh *查看磁盘挂载信息 df -lhT查看某个目录挂载在哪个分区,以及分区的磁盘使用情况 df [目录] #例如:df /ho…

【Linux】cd 命令使用

cd(英文全拼:change directory)命令用于改变当前工作目录的命令,切换到指定的路径。 ~ 也表示为 home 目录 的意思。. 则是表示目前所在的目录。.. 则表示目前目录位置的上一层目录。 语法 cd [目录] 命令选项及作用 执行令 …

平均模式恒流控制的LED驱动器:FP7122,打造舒适照明环境的绝佳选择

目录 一、 FP7122概述 二、 FP7122特点 三、 FP7122应用 近年来,随着LED照明技术的迅猛发展,LED驱动器在家庭照明、商业照明以及植物灯等领域扮演着至关重要的角色。其中,平均模式恒流控制的LED驱动器已经成为人们追求舒适照明环境的首选。…

齐活!Spring工程整合Redis实战汇总

🎈个人公众号:🎈 :✨✨✨ 可为编程✨ 🍟🍟 🔑个人信条:🔑 知足知不足 有为有不为 为与不为皆为可为🌵 🍉本篇简介:🍉本篇记录Spring工程整合Redis实战汇总操作&#xff0…

经典神经网络——GoogLeNet模型论文详解及代码复现

论文地址:[1409.4842] Going Deeper with Convolutions (arxiv.org) 一、GoogLeNet概述 创新点 我认为,这篇文章最大的创新点是引入了一个名为Inception块的结构,能够增加神经网络模型大小的同时,减缓参数量的爆炸式增长&#x…

Java第二十章 ——多线程

本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 在这之前,首先让我们来了解下在操作系统中进程和线程的区别:   进程:每个进程都有独立的代码和数据空间(进程上下文…

服务化通信OPC实操

实操也是基于视频进行一些笔记,没得写就少写了 准备 Nuget包准备:OPCfoundation 一般都是使用Ua,当然也是有: 客户端链接服务器参数:IP Port 认证 登录用户名 Session 的实例化创建 进行使用: 因为Ses…

NAS层协议学习(三)

消息结构 每个NAS消息包含一个协议鉴别符和一个消息标识。协议鉴别符是一个 4 位值,指示正在使用的协议,即对于 EPS NAS 消息是 EMM 或 ESM。消息标识指示发送的特定消息。 EMM 消息还包含一个安全标头,指示消息是否受到完整性保护和/或加密…

AI视觉识别有哪些工业应用

AI视觉识别,主要是利用人工智能算法对图像或视频数据进行分析和处理,以提取关键信息并执行筛选、判断、预警等任务。AI视觉识别涵盖多种应用,如人脸识别、目标检测和识别、图像分割、行为识别、视频分析等。本篇就简单介绍一下AI视觉识别的应…

Dockerfile讲解

Dockerfile 1. 构建过程解析2. Dockerfile常用保留字指令3. 案例3.1. 自定义镜像mycentosjava83.2. 虚悬镜像 4. Docker微服务实战 dockerfile是用来构建docker镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本。 dockerfile定义了进程需要的一切东西&…

OpenAI神秘项目Q-star曝光,人类永生或灭绝,将在我们有生之年发生

上周,OpenAI人事风波暂停的尾声中,有个“可能威胁人类”的、代号为“Q*”的神秘项目被抛掷出来。 传言中,Sam Altman被解雇前,几名研究人员向董事会发了一封信,警告一项强大的人工智能发现可能威胁到人类,而…

网络渗透测试(认识)

ARP协议 逻辑地址变成物理地址 32bit的IP地址变换成48bit的mac地址 ARP两个字节(0x0806) ARP解析协议 每一个主机都有ARP高速缓存,此缓存中记录了最近一段时间的内其他IP地址与其MAC地址的对应关系 如果本机想与某台主机通信,首先…