链表的讲解与单链表的实现

链表的讲解与单链表的实现

  • 一、链表的概念及结构
  • 二、链表的分类
  • 三、单链表的实现(使用VS2022)
    • 1.销毁、打印内容
    • 2.尾插尾删、头插头删
      • 尾插
      • 尾删
      • 头插
      • 头删
    • 3.查找、指定插入、指定删除
      • 查找
      • 指定插入
      • 指定删除
    • 4.目标后一个插入、目标后一个删除
  • 四、完整 SList.c 源代码

一、链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
在这里插入图片描述
每个元素都是独立申请下来的空间,被称为“节点/结点”。

节点的组成主要有两个部分:当前节点要保存的数据和保存下一个节点的地址(指针变量)。

链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点

typedef int SLTDataType;

typedef struct SLTNode
{
	SLTDataType data;		// 节点数据
	struct SLTNode* next;	// 下一个数据的地址
} SLTNode;

当我们想要保存一个整型数据时,实际是向操作系统申请了一块内存,这个内存不仅要保存整型数据,也需要保存下一个节点的地址(当没有下一个节点时保存的地址为空)。

当我们想要从第一个节点走到最后一个节点时,只需要在前一个节点拿上下一个节点的地址就可以了。

另外需要注意:

1、链式机构在逻辑上是连续的,在物理结构上不一定连续。

2、节点一般是从堆上申请的

3、从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能连续,可能不连续

二、链表的分类

链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:
在这里插入图片描述
链表说明:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:单链表和双向带头循环链表

1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2.带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现其结构会带来很多优势,实现反而简单。

三、单链表的实现(使用VS2022)

这里用VS2022的C语言来实现单链表。

单链表常用的增删改查等接口包括:

1.销毁、打印内容

2.尾插尾删、头插头删

3.查找、指定插入、指定删除

4.目标后一个插入、目标后一个删除

在 SList.h 中:

#pragma once

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

typedef int SLTDataType;

typedef struct SLTNode
{
	SLTDataType data;		// 节点数据
	struct SLTNode* next;	// 下一个数据的地址
} SLTNode;

// 1.销毁、打印
void SListDestroy(SLTNode* phead);

void SListPrint(SLTNode* phead);

// 2.尾插尾删、头插头删
void SListPushBack(SLTNode** pphead, SLTDataType x);

void SListPopBack(SLTNode** pphead);

void SListPushFront(SLTNode** pphead, SLTDataType x);

void SListPopFront(SLTNode** pphead);

// 3.查找、指定插入、指定删除
SLTNode* SListFind(SLTNode* phead, SLTDataType x);

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

void SListErase(SLTNode** pphead, SLTNode* pos);

// 4.目标后一个插入、目标后一个删除
void SListInsertAfter(SLTNode* pos, SLTDataType x);

void SListEraseAfter(SLTNode* pos);

1.销毁、打印内容

void SListDestroy(SLTNode* phead)
{
	SLTNode* temp = phead;
	while (temp)
	{
		SLTNode* next = temp->next;
		free(temp);
		temp = next;
	}
	phead = NULL;
}

void SListPrint(SLTNode* phead)
{
	for (SLTNode* temp = phead; temp; temp = temp->next)
	{
		printf("%d ", temp->data);
	}
	printf("\n");
}

2.尾插尾删、头插头删

尾插

// 节点创建封装为函数
SLTNode* CreateNode(SLTDataType x)
{
	SLTNode* temp = (SLTNode*)malloc(sizeof(SLTNode));
	if (temp == NULL)
	{
		perror("malloc failed");
		return NULL;
	}
	temp->data = x;
	temp->next = NULL;
	
	return temp;
}

void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	
	SLTNode* newNode = CreateNode(x);
	// 单链表要考虑头节点为空的情况
	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		// 寻找尾节点
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tail = tail->next;
		}

		// 将尾节点next指向新节点
		tail->next = newNode;
	}
}

尾删

void SListPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);	// 删除时头节点不能为空
	
	// 头节点下一个为空,直接删除头节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		// 尾部前面第一个节点
		SLTNode* tailFront = *pphead;
		while (tailFront->next->next)
		{
			tailFront = tailFront->next;
		}
		// 释放目标尾节点,将新一个尾节点next置空
		free(tailFront->next);
		tailFront->next = NULL;
	}
}

头插

void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	SLTNode* newNode = CreateNode(x);
	// 头节点为空或不为空都适用
	newNode->next = *pphead;
	*pphead = newNode;
}

头删

void SListPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);

	SLTNode* temp = (*pphead)->next;
	free(*pphead);
	*pphead = temp;
}

3.查找、指定插入、指定删除

查找

SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	// 找到返回地址,反之返回空指针
	for (SLTNode* temp = phead; temp; temp = temp->next)
	{
		if (temp->data == x)
		{
			return temp;
		}
	}
	return NULL;
}

指定插入

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);					// 限定pos不能为空,则不能尾插

	SLTNode* newNode = CreateNode(x);
	
	/*
	*	这里要考虑3种情况
	*	1.头节点为空,直接赋值
	*	2.头节点与目标节点相同,头插
	*	3.遍历链表,寻找到目标节点后插入
	*/
	
	if ((*pphead) == NULL)			// 1.头节点为空,直接赋值
	{
		*pphead = newNode;
	}
	else if ((*pphead) == pos)		// 2.头节点与目标节点相同,头插
	{
		newNode->next = (*pphead);
		(*pphead) = newNode;
	}
	else							// 3.遍历链表,寻找到目标节点后插入
	{
		SLTNode* temp = *pphead;
		while (temp->next != pos)
		{
			temp = temp->next;
		}
		newNode->next = pos;
		temp->next = newNode;
	}
}

指定删除

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	
	if (*pphead == pos)			// 1.头节点与目标节点相同
	{
		SLTNode* temp = (*pphead)->next;
		free(*pphead);
		*pphead = temp;
	}
	else						// 2.遍历链表后删除目标节点
	{
		SLTNode* temp = *pphead;
		while (temp->next != pos)
		{
			temp = temp->next;
		}
		temp->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

4.目标后一个插入、目标后一个删除

void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newNode = CreateNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}

void SListEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);	// 后一个不能为空

	SLTNode* temp = pos->next->next;
	free(pos->next);
	pos->next = temp;
}

四、完整 SList.c 源代码

#include "SList.h"

void SListDestroy(SLTNode* phead)
{
	SLTNode* temp = phead;
	while (temp)
	{
		SLTNode* next = temp->next;
		free(temp);
		temp = next;
	}
	phead = NULL;
}

void SListPrint(SLTNode* phead)
{
	for (SLTNode* temp = phead; temp; temp = temp->next)
	{
		printf("%d ", temp->data);
	}
	printf("\n");
}

// 节点创建封装为函数
SLTNode* CreateNode(SLTDataType x)
{
	SLTNode* temp = (SLTNode*)malloc(sizeof(SLTNode));
	if (temp == NULL)
	{
		perror("malloc failed");
		return NULL;
	}
	temp->data = x;
	temp->next = NULL;

	return temp;
}

void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	SLTNode* newNode = CreateNode(x);
	// 单链表要考虑头节点为空的情况
	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		// 寻找尾节点
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tail = tail->next;
		}

		// 将尾节点next指向新节点
		tail->next = newNode;
	}
}

void SListPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);	// 删除时头节点不能为空

	// 头节点下一个为空,直接删除头节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		// 尾部前面第一个节点
		SLTNode* tailFront = *pphead;
		while (tailFront->next->next)
		{
			tailFront = tailFront->next;
		}
		// 释放目标尾节点,将新一个尾节点next置空
		free(tailFront->next);
		tailFront->next = NULL;
	}
}

void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	SLTNode* newNode = CreateNode(x);
	// 头节点为空或不为空都适用
	newNode->next = *pphead;
	*pphead = newNode;
}

void SListPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);

	SLTNode* temp = (*pphead)->next;
	free(*pphead);
	*pphead = temp;
}


SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	// 找到返回地址,反之返回空指针
	for (SLTNode* temp = phead; temp; temp = temp->next)
	{
		if (temp->data == x)
		{
			return temp;
		}
	}
	return NULL;
}

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);					// 限定pos不能为空,则不能尾插

	SLTNode* newNode = CreateNode(x);

	/*
	*	这里要考虑3种情况
	*	1.头节点为空,直接赋值
	*	2.头节点与目标节点相同,头插
	*	3.遍历链表,寻找到目标节点后插入
	*/

	if ((*pphead) == NULL)			// 1.头节点为空,直接赋值
	{
		*pphead = newNode;
	}
	else if ((*pphead) == pos)		// 2.头节点与目标节点相同,头插
	{
		newNode->next = (*pphead);
		(*pphead) = newNode;
	}
	else							// 3.遍历链表,寻找到目标节点后插入
	{
		SLTNode* temp = *pphead;
		while (temp->next != pos)
		{
			temp = temp->next;
		}
		newNode->next = pos;
		temp->next = newNode;
	}
}

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);

	if (*pphead == pos)			// 1.头节点与目标节点相同
	{
		SLTNode* temp = (*pphead)->next;
		free(*pphead);
		*pphead = temp;
	}
	else						// 2.遍历链表后删除目标节点
	{
		SLTNode* temp = *pphead;
		while (temp->next != pos)
		{
			temp = temp->next;
		}
		temp->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newNode = CreateNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}

void SListEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);	// 后一个不能为空

	SLTNode* temp = pos->next->next;
	free(pos->next);
	pos->next = temp;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一个简约而不简单的记账 App(一刻记账)

前言 在两年多前, 我曾经写过一个本地化的记账 App, 当时没有想过以后的发展. 全程是本地化的, 当时主要是为了练习 Compose 而写的. TallyApp 而现在一个完整的记账 App 它来了 一刻记账 算是圆了我两年前的梦了吧. 也让我可以真正的使用自己的记账软件. 下面是 一刻记账 记…

联发科COMPUTEX展台有看点,2024年最热AI技术都在这里

近日&#xff0c;备受瞩目的COMPUTEX 2024科技展会开幕&#xff0c;联发科在COMPUTEX展出了先进AI技术成果和AI在广泛领域的创新应用。从不久前召开的天玑开发者大会MDDC来看&#xff0c;联发科的AI关键技术和AI应用生态早已遍地开花&#xff0c;全面覆盖智能手机、平板电脑、汽…

【图 - 遍历(BFS DFS)】深度优先搜索算法(Depth First Search), 广度优先搜索算法(Breadth First Search)

图的深度优先搜索(Depth First Search)&#xff0c;和树的先序遍历比较类似; 广度优先搜索算法(Breadth First Search)&#xff0c;又称为"宽度优先搜索"或"横向优先搜索"。 深度优先搜索 深度优先搜索介绍深度优先搜索图解有向图的深度优先搜索广度优先搜…

vue2中封装图片上传获取方法类(针对后端返回的数据不是图片链接,只是图片编号)

在Vue 2中实现商品列表中带有图片编号,并将返回的图片插入到商品列表中,可以通过以下步骤完成: 在Vue组件的data函数中定义商品列表和图片URL数组。 创建一个方法来获取每个商品的图片URL。 使用v-for指令在模板中遍历商品列表,并显示商品名称和图片。 下面是一个简单的Vu…

【Vue】成绩案例

文章目录 一、功能描述二、思路分析三、完整代码 一、功能描述 1.渲染功能 2.删除功能 3.添加功能 4.统计总分&#xff0c;求平均分 二、思路分析 渲染功能 v-for :key v-bind:动态绑定class的样式&#xff08;来回切换&#xff09; 删除功能 v-on绑定事件&#xff0c; 阻止…

JVM学习-MAT

MAT(Memory Analyzer Tool) 基本概述 Java堆内存分析器&#xff0c;可以用于查找内存泄漏以及查看内存消耗情况MAT是基于Eclipse开发的&#xff0c;不仅可以单独使用&#xff0c;还能以插件方式嵌入Eclipse中使用&#xff0c;是一款免费的性能分析工具 获取堆dump文件 dump…

目标检测数据集 - 智能零售柜商品检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;智能零售柜商品检测数据集&#xff0c;真实智能零售柜监控场景采集高质量商品图片数据&#xff0c;数据集含常见智能零售柜商品图片&#xff0c;包括罐装饮料类、袋装零食类等等。数据标注标签包含 113 个商品类别&#xff1b;适用实际项目应用&#xff…

K8s Pod的QoS类

文章目录 OverviewPod的QoS分类Guaranteed1.如何将 Pod 设置为保证Guaranteed2. Kubernetes 调度器如何管理Guaranteed类的Pod Burstable1. 如何将 Pod 设置为Burstable2.b. Kubernetes 调度程序如何管理 Burstable Pod BestEffort1. 如何将 Pod 设置为 BestEffort2. Kubernete…

【CUDA】保姆级用学校服务器远程编写运行CUDA代码-jupyter

用学校服务器远程编写运行CUDA代码 0 前言1 检查当前系统是否支持CUDA2 在 Jupyter 中编写和执行代码3 打开终端 激活pytorch环境4 创建新文件&#xff08;.cu格式&#xff09;5 执行代码 0 前言 关于如何用xshell连学校服务器&#xff0c;我在之前的博客中已经详细介绍了&…

从零开始利用MATLAB进行FPGA设计(七)用ADC采集信号教程2

黑金的教程做的实在太拉闸了&#xff0c;于是自己摸索信号采集模块的使用方法。 ADC模块&#xff1a;AN9238 FPGA开发板&#xff1a;AX7020&#xff1b;Xilinx 公司的 Zynq7000 系列的芯片XC7Z020-2CLG400I&#xff0c;400引脚 FBGA 封装。 往期回顾&#xff1a; 从零开始利…

【计算机毕设】基于SpringBoot的个人理财系统设计与实现 - 源码免费(私信领取)

免费领取源码 &#xff5c; 项目完整可运行 &#xff5c; v&#xff1a;chengn7890 诚招源码校园代理&#xff01; 1. 研究目的 个人理财管理对于现代人来说越来越重要&#xff0c;随着金融产品和消费方式的多样化&#xff0c;人们需要一个方便、高效、安全的工具来管理和规划自…

Python中数字比较与获取较大值的深入解析

目录 一、引言 二、Python数字类型概述 三、数字比较操作符 四、获取较大值的逻辑与实现 五、高级话题&#xff1a;使用内置函数和库 六、性能分析与优化 七、案例分析 八、总结与展望 一、引言 在编程世界中&#xff0c;数字的比较和获取较大值是基础且常见的操作。P…

UKP3D,工程文件怎么判断是否保存数据过?

湖南中南勘测某用户因在使用其他软件时造成死机退出&#xff0c;再打开我们软件时发现设计库为空&#xff1b;用户确定保存过很多次&#xff0c;用户很着急。 凡是保存后的数据&#xff0c;这个MAXELEMENT 的值是通过 节点的编号相加的。所以这个值都是0时&#xff0c;意味着没…

woodward控制器维修变压器差动保护器ESDR405TB

WOODWARD伍德沃德控制器保护器维修ESDR4T系列LR20021&#xff1b;LR20476&#xff1b;MFR1&#xff1b;LR20949&#xff1b;UMT145B/A3&#xff1b;MFR1345B。 伍德沃德MFR1系列控制器维修&#xff1b;多功能继电保护器维修&#xff1b;用于船舶,电厂待工业控制机器设备。 WOO…

生命周期钩子小案例

文章目录 一、在created中发送数据二、在mounted中获取焦点 一、在created中发送数据 <body><div id"app"><ul><li v-for"(item, index) in list" :key"item.id" class"news"><div class"left"…

Tomcat服务器|创建java web项目

文章目录 Tomcat是什么&#xff1f;下载启动Tomcat使用maven创建java web项目集成本地Tomcat例子注意事项启动tomcat控制台乱码改端口 Tomcat是什么&#xff1f; Apache Tomcat&#xff0c;通常简称为Tomcat&#xff0c;是一个开源的Web服务器和Servlet容器。Tomcat主要用来运…

kettle从入门到精通 第六十五课 ETL之kettle 执行动态SQL语句,轻松实现全量增量数据同步

本次课程的逻辑是同步t1表数据到t2表&#xff0c;t1和t2表的表机构相同&#xff0c;都有id&#xff0c;name,createtime三个字段。 CREATE TABLE t1 (id bigint NOT NULL AUTO_INCREMENT,name varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,cr…

视创云展虚拟展厅的6大优势,感受3D数字化带来的无限可能!

在数字化浪潮的推动下&#xff0c;视创云展线上数字展厅以其独特的魅力&#xff0c;正逐步成为企业营销宣传的新窗口。它利用互联网技术&#xff0c;将实体展览馆的内容以数字化的形式呈现&#xff0c;打破了时间和空间的限制&#xff0c;让更多人能够随时随地畅游参观&#xf…

SwiftUI中LazyVGrid和LazyHGrid的介绍以及GridItem的用法

在SwiftUI中&#xff0c;我们可以使用LazyVGrid或LazyHGrid视图创建一个二维响应列表。如果我们想要一个垂直网格&#xff0c;我们可以使用LazyVGrid视图&#xff0c;如果我们想要一个水平网格&#xff0c;可以使用LazyHGrid视图。这些视图允许我们创建一个网格的项目&#xff…

Aethir: 破局算力瓶颈,构建AI时代去中心化云基础设施

科技的每一次飞跃都在重新塑造世界&#xff0c;而近年来&#xff0c;跨越式的技术革新再次引发了深刻的变革&#xff0c;那就是人工智能&#xff08;AI&#xff09;。 人工智能已然超越了此前的所有技术概念&#xff0c;成为了继互联网之后的下一个巨大浪潮。从自动驾驶汽车到…