基础数据结构链表

news2024/11/17 15:41:56

链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用。链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。链表都有一个头指针,一般以head来表示,存放的是一个地址。链表中的节点分为两类,头结点和一般节点,头结点是没有数据域的。链表中每个节点都分为两部分,一个数据域,一个是指针域。说到这里你应该就明白了,链表就如同车链子一样,head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。

作为有强大功能的链表,对他的操作当然有许多,比如:链表的创建,修改,删除,插入,输出,排序,反序,清空链表的元素,求链表的长度等等。

单链表的存储结构就像铁链一样

但是他们在物理地址上并不一定是相邻的

 

带头节点的就是有个头部,我们可以通过头部来对这个链表进行访问

而不带头结点的单链表我们就需要使用二级指针来访问这个单链表的

 

 带头结点的单链表的结构就像这样

每一个结点都有自己的地址 然后是自己的数据域 指向下个结点的指针域

因此我们就可以得到单链表的结构体设计结构


typedef int ELEM_TYPE;

typedef struct Node
{
	ELEM_TYPE data;//数据域(保存数据的有效值)
	struct Node* next;//指针域(保存着下一个有效节点的地址)
}Node, *PNode;

 还有单链表对应的函数

因为单链表在后续的使用过程中相对比较重要

因此这里我们就需要将单链表的完整代码给出

void Init_list(struct Node* plist);

//头插
bool Insert_head(PNode plist, ELEM_TYPE val);

//尾插
bool Insert_tail(PNode plist, ELEM_TYPE val);

//按位置插
bool Insert_pos(PNode plist, int pos, ELEM_TYPE val);

//头删
bool Del_head(PNode plist);

//尾删
bool Del_tail(PNode plist);

//按位置删
bool Del_pos(PNode plist, int pos);

//按值删
bool Del_val(PNode plist, ELEM_TYPE val);

//查找 //查找到,返回的是查找到的这个节点的地址
struct Node* Search(PNode plist, ELEM_TYPE val);

//判空
bool IsEmpty(PNode plist);

//清空
void Clear(PNode plist);

//销毁1
void Destroy(PNode plist);
//销毁2
void Destroy2(PNode plist);

//打印
void Show(PNode plist);

//获取有效值个数
int GetLength(PNode plist);

接下来我们就要逐步实现单链表

首先是初始化

头节点我们只需要使用指针域 并不使用数据域

因此我们不初始化数据域

后续的结点可以通过购买获得

而且因为点链表中的结点是通过指针连接 因此我们并不需要对其进行大小的给定 可以在使用时调整

void Init_list(struct Node* plist)
{
	//1.判断plist是否为NULL地址
	assert(plist != NULL);

	//2.对plist指向的头结点里面的每一个成员变量进行赋值
	//3.因为头结点直接借用的是有效节点的结构体设计,省事,但是多了一个数据域用不到,
	//   既然头结点的数据域用不到,那就浪费掉,只用指针域即可

	//plist->data;  头结点的数据域不使用
	plist->next = NULL;

然后我们就可以得到他的基础结构

这里头节点的地址我们为了方便可以随机给一个值

但是这里是仅仅为了方便 实际上头节点的地址我们是未知也不需要知道的

然后是插入函数

我们先写头插 因为这里的特殊情况较少

bool Insert_head(PNode plist, ELEM_TYPE val)
{
	//0.安全性处理
	assert(plist != NULL);

	//1.购买新节点
	struct Node* pnewnode = (struct Node*)malloc(1 * sizeof(struct Node));
	assert(pnewnode != NULL);
	pnewnode->data = val;

	//2.找到合适的插入位置(其实就是用柱子很指向插入位置上一个节点)
	//因为是头插,永远都是插入在头结点后面,所以不用找  直接用plist即可

	//3.插入
	pnewnode->next = plist->next;
	plist->next = pnewnode;

	return true;
}

 

我们可以看到因为每一个结点都有指针域 因此我们在头插的时候就需要改变前一个的next域和购买的新指针指向下一个结点 保持单链表的结点的连续性

 

尾插就相对简单 因为尾插后插入的新结点就作为目前单链表的结尾 因此它的next域就可以为NULL

bool Insert_tail(PNode plist, ELEM_TYPE val)
{
	//0.安全性处理
	assert(plist != NULL); //判断plist指向的单链表的头结点是否存在

	//1.购买新节点
	struct Node *pnewnode = (struct Node *)malloc(1 * sizeof(struct Node));
	assert(pnewnode != NULL);
	pnewnode->data = val;
	//pnewnode->next = NULL;  //这行代码可以省略,也可以留下

	//2.找到合适的插入位置 (找到在哪一个结点后面插入,然后用指针p指向这个结点)
	struct Node *p = plist;   //这里p指向头结点  还是指向第一个有效结点,下面会总结:
	for(; p->next!=NULL; p=p->next);

	//3.正常插入即可
	pnewnode->next = p->next;
	p->next = pnewnode;

	return true;
}

 这里就涉及到了单链表中常见的两种访问结点的循环方式

我们可以根据情况改变使用方法

这个循环在后续的使用中会非常常见 因此我们要牢记两种的使用情景

这里我们使用的就是需要前驱的循环

然后就是按位置插入

如果是 0 就是头插

bool Insert_pos(PNode plist, int pos, ELEM_TYPE val)
{
	//0.安全性处理
	assert(plist != NULL); //判断plist指向的单链表的头结点是否存在
	assert(pos>=0 && pos<=GetLength(plist));

	//1.购买新节点
	struct Node *pnewnode = (struct Node *)malloc(1 * sizeof(struct Node));
	assert(pnewnode != NULL);
	pnewnode->data = val;
	//pnewnode->next = NULL;  //这行代码可以省略,也可以留下

	//2.找到合适的插入位置 (找到在哪一个结点后面插入,然后用指针p指向这个结点)
	//  发现规律:pos=几  则让指针p从头结点开始向后走,走pos步即可
	struct Node *p = plist;
	for(int i=0; i<pos; i++)
	{
		p = p->next;
	}

	//3.正常插入
	pnewnode->next = p->next;
	p->next = pnewnode;

	return true;
}

 如果是任意位置 我们就带头节点的for循环来访问

我们先访问到待插结点的前一个位置 然后正常插入

 改变 next域即可

这里涉及到了统计结点个数的函数

我们一并实现

实际就是用不带头结点的for循环即可

int GetLength(PNode plist) {
	int count = 0;
	for (struct Node* p = plist->next; p != NULL; p = p->next) {
		count++;
	}
	return count;
}

 

接下来是删除

其实在插入和删除前我们都要考虑到是否为 空 或者是否为满

因为单链表不存在满的情况 因此我们在插入时并没做判断

但是单链表存在空的情况

因此我们需要在删除前进行判断 

bool Del_head(PNode plist)
{
	//0.安全性处理  不仅仅判断头结点是否存在,还需要判断是否是空链表
	assert(plist != NULL);
	if(IsEmpty(plist))
	{
		return false;
	}
	//如果不是空链表,则代表至少有一个有效节点

	//1.申请一个临时指针p指向待删除节点
	struct Node *p = plist->next;//头删比较特殊,待删除节点就是第一个有效节点
	 
	//2.申请一个临时指针q指向待删除节点的上一个节点(前驱)
	//头删比较特殊,待删除节点的上一个节点就是头结点,所以这里q不用定义,直接使用plist即可

	//3.跨越指向
	plist->next = p->next;

	//4.释放待删除节点
	free(p);

	return true;
bool IsEmpty(PNode plist)
{
	return plist->next == NULL;
}

 

 

 跨域指向就是把待删除结点的后一个结点的地址赋值给前一个结点的next域

尾删就相对简单 我们将倒数第二个元素的next域指向NULL 然后释放最后一个结点 就可以实现

bool Del_tail(PNode plist)
{
	//0.安全性处理  不仅仅判断头结点是否存在,还需要判断是否是空链表
	//如果不是空链表,则代表至少有一个有效节点
	assert(plist != NULL);
	if(IsEmpty(plist))
	{
		return false;
	}

	//1.申请一个临时指针p指向待删除节点   p指向倒数第一个节点(尾结点)
	struct Node *p = plist;
	for(; p->next!=NULL; p=p->next);
	//此时,for循环执行结束,指针p指向尾结点

	//2.申请一个临时指针q指向待删除节点的上一个节点(前驱)  q指向倒数第二个节点
	struct Node *q = plist;
	for(; q->next!=p; q=q->next);	//for( ; q->next->next!=NULL; q=q->next);

	//3.跨越指向
	q->next = p->next;

	//4.释放待删除节点
	free(p);

	return true;
}

这里找到倒数第二个元素时我们就可以使用 先找到最后一个结点 再通过 最后一个结点找倒数第二个结点

然后是按位置删除

bool Del_pos(PNode plist, int pos)
{
	//0.安全性处理   
	assert(plist!=NULL);
	assert(pos>=0 && pos<GetLength(plist));

	//这里先找q,再找p
	//1.申请一个临时指针q指向待删除节点的上一个节点(前驱) 
	struct Node *q = plist;
	for(int i=0; i<pos; i++)
	{
		q = q->next;
	}

	//2.申请一个临时指针p指向待删除节点,将q的next给p
	struct Node *p = q->next;

	//3.跨越指向
	q->next = p->next;

	//4.释放
	free(p);

	return true;
}

 

然后还有一个按值删除 就是对应数据域

我们就先要实现一个查找函数

struct Node* Search(PNode plist, ELEM_TYPE val)
{
	//判断使用不需要前驱的for循环,只要将单链表遍历一遍即可

	for(struct Node *p=plist->next; p!=NULL; p=p->next)
	{
		if(p->data == val)
		{
			return p;
		}
	}

	return NULL;
}
bool Del_val(PNode plist, ELEM_TYPE val)
{
	//1.先判断这个值是否存在于单链表

	struct Node *p = Search(plist, val);
	if(p == NULL)
	{
		return false;
	}

	//此时,如果p!=NULL,则代表val这个节点存在,且现在还被指针p指向
	//这时待删除节点找到了,则接下来需要找待删除节点的上一个节点,用q指向

	struct Node *q = plist;
	for(; q->next!=p; q=q->next);
	//此时,指针q也找到了,现在q指向了待删除结点的上一个结点了

	//p和q现在都找到了,则跨越指向+释放
	q->next = p->next;

	free(p);

	return true;
}

 然后就是正常普通的删除

最后我们使用完整个单链表

我们可以 销毁这个单链表

void Destroy(PNode plist)
{
	/*while(!IsEmpty(plist))
	{
		Del_head(plist);
	}*/

	while(plist->next != NULL)
	{
		struct Node *p = plist->next;
		plist->next = p->next;
		free(p);
	}

}

//销毁2   
void Destroy2(PNode plist)
{
	//0.安全性处理

	//1.定义两个指针p和q  p指向第一个有效节点 q先不要赋值  
	struct Node *p = plist->next;
	struct Node *q = NULL;

	//2.断开头结点 因为不借助头结点,所以一开始就将头结点变成最终销毁完成后的样子
	plist->next = NULL;

	//3.两个指针p和q合作,循环释放后续节点
	while(p != NULL)
	{
		q = p->next;
		free(p);
		p = q;
	}
}

这里有两种方式 最能够想到的就是一直头删 因为头删的时间复杂度小 因此使用头删

还有一种就是 通过两个指针合作来一个一个的删除

这就是单链表的全部代码 

 

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

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

相关文章

论文速递:带重力约束的点云配准(Gravity-constrained point cloud registration)

标题&#xff1a;Gravity-constrained point cloud registration 作者&#xff1a;Vladim ́ır Kubelka, Maxime Vaidis and Franc ̧ois Pomerleau&#xff0c;加拿大拉瓦尔大学 来源&#xff1a;IROS 2022 摘要 视觉和激光SLAM算法从IMU中获益良多。高频率的IMU数据弥补了…

嵌入式:ARM的流水线技术

三级流水线ARM的组织 ARM的3级流水线介绍 到ARM7为止的ARM处理器使用的简单3级流水线分别为 取指级 &#xff1a;读取指令。译码级 &#xff1a;对指令进行译码。占有“译码逻辑”&#xff0c;不占有“数据路径”。执行级 &#xff1a;指令占有“数据路径”&#xff0c;寄存…

拼搏别样的未来,中国社科院与美国杜兰大学金融管理硕士项目助力你的人生旅程

你憧憬中的未来是什么样子呢&#xff1f;我们的人生路程充满了众多可能性&#xff0c;只要努力就会收获自己的别样人生。我们的人生不是单一的色彩&#xff0c;它是五彩斑斓、精彩纷呈的。在每一个阶段的我们所拥有当时的状态并不能代表永远&#xff0c;随着我们的拼搏与奋斗&a…

2022全年度空调十大热门品牌销量榜单

今年空调的销售得到明显改善&#xff0c;尤其是今年夏天全国多地最高气温同比明显提升&#xff0c;且高温的强度和持续时间还具有一定的极端性。随着气温的骤升&#xff0c;空调市场也迅速升温&#xff0c;各终端销售量出现明显增长。 根据鲸参谋数据统计&#xff0c;今年京东平…

“美亚杯”第五届中国电子数据取证大赛答案解析(个人赛)

A C D 分区5为系统分区 A 1073741824*458.29492085140520.96 E A A E D B 无答案 C 搜索各选项&#xff0c;C项搜不到 B C E 使用火绒剑查看进程调用的动态链接库 C 仿真得 B 内存大小为3G&#xff0c;在系统盘根目录过滤大小在2-8G之间的文件 D Win10时间线信息存放的数…

代码随想录算法训练营第六天| 哈希表理论基础 ,242.有效的字母异位词 , 349. 两个数组的交集 ,202. 快乐数,1. 两数之和

代码随想录算法训练营第六天| 哈希表理论基础 &#xff0c;242.有效的字母异位词 &#xff0c; 349. 两个数组的交集 &#xff0c;202. 快乐数&#xff0c;1. 两数之和 哈希表理论基础 建议&#xff1a;大家要了解哈希表的内部实现原理&#xff0c;哈希函数&#xff0c;哈希碰…

PAT 乙级 1078字符串压缩与解压 python

题目 思路&#xff1a; 生成两个函数&#xff1a;压缩与解压 在压缩中利用flag保留前一个字符串 再利用count统计重复出现字母的个数 如果目前遍历到的字母和flag相同&#xff0c;则count1 反之&#xff0c;说明重复结束 在解压中&#xff0c;如果遇到数字&#xff0c;表示需…

一文带你了解 K8s 是如何部署应用的

通过部署一个 Nginx 服务/实例来简单介绍 K8s 部署一个应用的流程。 1、创建 deployment 资源 kubectl create deployment nginx --imagenginx kubectl expose deployment nginx --port80 --typeNodePort2、查看 deployment 资源 kubectl get deployment3、查看节点上的 pod…

安全需求分析

汽车制造业 MES系统 DNC系统 生产 安全域1 管理层 工控安全隔离装置 交换机 安全配置核查系统 HMI 历史数据库 运行监控系统 实时数据库 打印机 过程 安全域2 监控层 工控漏洞扫描系统 安全交换机 工控安全审计系统 工控入侵检测系统 工程师站 A 操作员站 A 实时数据库A 操作员…

【菜鸡读论文】Margin-Mix: Semi-Supervised Learning for Face Expression Recognition

【菜鸡读论文】Margin-Mix: Semi-Supervised Learning for Face Expression Recognition 感觉最近的每天都在见证历史&#xff0c;上海现在也开始全面放开了&#xff0c;很多高校都已经开始遣返了。小伙伴们都回到家了吗&#xff1f; 上周周末太懒了&#xff0c;就没有更新论…

一文教你在SpringBoot中使用Thymeleaf

一文教你在SpringBoot中使用Thymeleaf1.快速使用Thymeleaf2.Thymeleaf快速入门案例3.Thymeleaf基本语法each遍历其他语法1.快速使用Thymeleaf 首先导入Maven依赖&#xff1a; <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thym…

怎么在图片加文字边框?这些方法值得你收藏

当我们在拍完照片以后&#xff0c;一般都会为它进行后期的编辑修图&#xff0c;因此在图片上面添加一些文字信息和边框是必不可少的&#xff0c;这样不仅能使图片变得更加精致&#xff0c;还可以增加它的信息量。那你们知道怎么给图片加上边框和文字吗&#xff1f;别着急&#…

uniapp实战仿写网易云音乐(一)—底部工具栏以及首页轮播图swiper的实现

文章目录前言首页导航栏公共样式的配置首页轮播图最后前言 从本篇文章开始记录uniapp实战仿写网易云音乐项目的过程&#xff0c;主要会写一下关键步骤和难点&#xff0c;本专栏会保持持续更新&#xff0c;并在最后送上源码&#xff0c;感兴趣的可以订阅本专栏。本篇主要实现的…

【C++11多线程】获取异步任务的结果:future、shared_future

文章目录1.std::future1.1 get()1.2 valid()1.3 share()1.4 wait_for()1.4.1 std::future_status::timeout1.4.2 std::future_status::ready1.4.3 std::future_status::deferred2.std::shared_future3.参考资料1.std::future std::future 是个类模板&#xff0c;其源码如下所示…

[附源码]Python计算机毕业设计大学生志愿者管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

【论文阅读】社交网络识别影响力最大节点方法综述-05

Vital nodes identification in complex networks识别单个重要节点的方法基于结构信息一、结构中心性&#xff08;Structural centralities&#xff09;1.度中心性&#xff08;基于邻域的中心性&#xff09;2.四阶邻居信息&#xff08;基于邻域的中心性&#xff09;3.ClusterRa…

html个人博客网站模板(源码)

文章目录1.设计来源1.1 首界面1.2 我的文章界面1.2 发表文章界面1.3 文章详细界面2.效果和源码2.1 目录结构2.2 源代码源码下载作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/128287493 html个人博客网站模板 html个人博…

多线程编程【POSIX信号量】

POSIX信号量&#x1f4d6;1. 信号量的定义&#x1f4d6;2. 二值信号量&#x1f4d6;3. 用信号量作为条件变量&#x1f4d6;4. 基于环形队列的生产者消费者模型我们知道&#xff0c;需要锁和条件变量来解决各种相关的&#xff0c;有趣的并发问题&#xff0c; Dijkstra及其同事发…

Redis限制一键登录次数

二、解决思路 因为项目实现一键登录采用的是 自有服务器调用 所以限制一键登录分为两步走&#xff0c;因为每个手机号有唯一的openid所以拿openid做redis的key值 &#xff08;1&#xff09;、调用云函数之前 调用云函数之前&#xff0c;前端会发起请求给后台&#xff0c;拿到这…

Metal每日分享,调整对比度滤镜效果

本案例的目的是理解如何用Metal实现调整对比度效果滤镜&#xff0c;调整对比度就是在保证平均亮度不变的情况下&#xff0c;扩大或缩小亮的点和暗的点之间的差异&#xff1b; Demo HarbethDemo地址 实操代码 // 对比度 let filter C7Contrast.init(contrast: 2.0)// 方案1:…