浅析链表结构

一、单向链表

        C语言中数组是常用的一种数据类型,但可惜数组长度是固定大小的,不能动态扩展,使用起来有时不是很方便。然后就有了自定义的动态数组结构,动态数组就比较好用了,长度可以任意扩展,但还有一个问题不好解决,就是每次插入数据时,数组后面的数据都得乾坤大挪移一回,如果数组长度较大的话效率就比较低了。再然后就有了链表结构的出现。链表的原理如下图所示:

具体代码如下:

#include <stdio.h>
#include <malloc.h>
#include <string.h>

// 链表中节点结构体
typedef struct _stu_linkNode
{
	void* data;						// 本节点存储的数据(由于不知道本节点中要存储何种类型数据,因此用万能指针void*来代表所有数据类型包括自定义类型。)
	struct _stu_linkNode* next;		// 下个节点的地址
} stu_linkNode;


// 链表结构体
typedef struct _stu_linkList
{
	stu_linkNode head;		// 链表头节点
	int size;				// 链表长度
} stu_linkList;

// 用万能指针来代替链表结构体,这是封装的关键
typedef void* linkList;

// 链表初始化
linkList linkListInit()
{
	// 在堆区开辟链表
	stu_linkList* pList = (stu_linkList*)malloc(sizeof(stu_linkList));
	if (pList == NULL) { return NULL; }
	
	// 设置初始大小
	pList->head.data = NULL;
	pList->head.next = NULL;
	pList->size = 0;

	return pList;
}

// 链表指定位置插入
void linkListInsert(linkList ll, int pos, void* val)
{
	if (ll == NULL) { return; }
	if (val == NULL) { return; }
	stu_linkList* pList = (stu_linkList*)ll;					// 强制转换
	if (pos < 0 || pos > pList->size) { pos = pList->size; }	// 位置不正确则默认尾插

	// 找到pos位置所在节点的前驱节点
	stu_linkNode* prevNode = &pList->head;	// 定义节点变量指向头节点(如果链表为空则前驱节点就是头节点)
	for (int i = 0; i < pos; i++)			// 循环改变节点变量指向,直至pos位置所在节点的前驱节点
	{
		prevNode = prevNode->next;			// 重点:prevNode是当前节点的指针地址,prevNode->next是下一个节点的指针地址
	}

	// 创建要插入的新节点
	stu_linkNode* newNode = (stu_linkNode*)malloc(sizeof(stu_linkNode));
	if (newNode == NULL) { return; }
	newNode->data = val;
	newNode->next = NULL;

	// 将新节点插入到链表中
	newNode->next = prevNode->next;
	prevNode->next = newNode;

	// 更新链表大小
	pList->size++;
}

// 链表尾插法
void linkListPushBack(linkList ll, void* val)
{
	if (ll == NULL) { return; }
	if (val == NULL) { return; }
	stu_linkList* pList = (stu_linkList*)ll;					// 强制转换
	linkListInsert(ll, pList->size, val);
}

// 链表指定位置删除
void linkListErase(linkList ll, int pos)
{
	if (ll == NULL) { return; }
	stu_linkList* pList = (stu_linkList*)ll;					// 强制转换
	if (pos < 0 || pos > pList->size - 1) { return; }			// 位置不正确则返回

	// 找到pos位置所在节点的前驱节点
	stu_linkNode* prevNode = &pList->head;
	for (int i = 0; i < pos; i++)
	{
		prevNode = prevNode->next;
	}
	
	// 得到当前pos所在节点
	stu_linkNode* delNode = prevNode->next;
	
	// 开始删除
	prevNode->next = delNode->next;
	free(delNode);
	delNode = NULL;
	
	// 更新元素大小
	pList->size--;
}

// 链表尾删法
void linkListPopBack(linkList ll)
{
	if (ll == NULL) { return; }
	stu_linkList* pList = (stu_linkList*)ll;					// 强制转换
	linkListErase(ll, pList->size - 1);
}

// 链表指定值删除(利用回调函数让用户自己去比较)
void linkListRemove(linkList ll, void* data, int (*myCompare)(void*, void*))
{
	if (ll == NULL) { return; }
	if (data == NULL) { return; }

	// 强制转换
	stu_linkList* pList = (stu_linkList*)ll;

	// 在遍历查找该值匹配的节点时还要记录该节点的前驱节点,因此这里我们用双指针。
	stu_linkNode* prevNode = &pList->head;			// 当前节点的前驱节点
	stu_linkNode* curNode = pList->head.next;		// 当前节点
	for (int i = 0; i < pList->size; i++)
	{
		if (myCompare(data, curNode->data))	// 找到了
		{
			prevNode->next = curNode->next;
			free(curNode);
			curNode = NULL;
			pList->size--;
			break;
		}

		// 未找到,双指针向后移动
		prevNode = curNode;							// 前驱节点指向当前节点
		curNode = curNode->next;					// 当前节点指向下一节点
	}
}

// 链表大小
int linkListSize(linkList ll)
{
	if (ll == NULL) { return -1; }
	stu_linkList* pList = (stu_linkList*)ll;		// 强制转换
	return pList->size;
}

// 链表遍历(利用回调函数)
void linkListForEach(linkList ll, int (*myForEach)(void*))
{
	if (ll == NULL) { return; }
	if (myForEach == NULL) { return; }
	
	stu_linkList* pList = (stu_linkList*)ll;		// 强制转换

	stu_linkNode* curNode = pList->head.next;	// 第一个节点
	for (int i = 0; i < pList->size; i++)
	{
		if (myForEach(curNode->data) == -1) { break; }	// 根据返回值判断是否中途退出遍历
		curNode = curNode->next;
	}
}

// 链表清空
void linkListClear(linkList ll)
{
	if (ll == NULL) { return; }

	// 强制转换	
	stu_linkList* pList = (stu_linkList*)ll;

	// 释放内部每个节点
	stu_linkNode* curNode = pList->head.next;
	for (int i = 0; i < pList->size; i++)
	{
		stu_linkNode* nextNode = curNode->next;	// 得到当前节点的后继节点
		free(curNode);
		curNode = nextNode;
	}
	
	// 将头节点的next设为NULL
	pList->head.next = NULL;

	// 更新元素大小
	pList->size = 0;
}

// 链表销毁
void linkListDestroy(linkList ll)
{
	if (ll == NULL) { return; }

	linkListClear(ll);

	free(ll);
	ll = NULL;
}

// 测试用结构体
struct _stu_person
{
	char name[31];
	int age;
};

// 测试用回调函数(返回-1则退出遍历)
int personPrint(void* val)
{
	struct _stu_person* p = (struct _stu_person*)val;
	printf("姓名:%s 年龄:%d\n", p->name, p->age);
	return 0;
}

// 测试用比较回调函数(1-成功,0-失败)
int personCompare(void* data1, void* data2)
{
	struct _stu_person* p1 = (struct _stu_person*)data1;
	struct _stu_person* p2 = (struct _stu_person*)data2;
	if (strcmp(p1->name,p2->name) == 0 && p1->age == p2->age)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

// 测试链表
void testLinkList()
{
	// 创建链表
	linkList list = linkListInit();

	// 测试数据
	struct _stu_person p1 = { "刘备",39 };
	struct _stu_person p2 = { "关羽",34 };
	struct _stu_person p3 = { "张飞",32 };
	struct _stu_person p4 = { "赵云",28 };
	struct _stu_person p5 = { "吕布",30 };

	// 开始插入
	linkListPushBack(list, &p1);
	linkListInsert(list, 10, &p2);
	linkListInsert(list, 1, &p3);
	linkListPushBack(list, &p4);
	linkListInsert(list, 0, &p5);

	// 遍历
	printf("=====元素个数:%d=====\n", linkListSize(list));
	linkListForEach(list, personPrint);

	// 删除指定位置数据
	linkListErase(list, 1);
	printf("\n=====删除第一个位置数据后的元素个数:%d=====\n", linkListSize(list));
	linkListForEach(list, personPrint);
	linkListPopBack(list);
	printf("\n=====删除最后位置数据后的元素个数:%d=====\n", linkListSize(list));
	linkListForEach(list, personPrint);

	// 删除指定值数据
	struct _stu_person pp = { "张飞",32 };
	linkListRemove(list, &pp, personCompare);
	printf("\n=====删除指定值【张飞,32】数据后的元素个数:%d=====\n", linkListSize(list));
	linkListForEach(list, personPrint);

	// 清空链表
	linkListClear(list);
	printf("\n=====链表清空后的元素个数:%d=====\n", linkListSize(list));
	linkListForEach(list, personPrint);

	// 销毁链表
	linkListDestroy(list);
	printf("\n=====链表已经销毁=====\n");

}

// 链表
int main()
{
	testLinkList();

	return 0;
}

二、双向链表

        单向链表已经基本实现了用户想要的功能,但是有一个问题啊,在插入或删除时都得查找该节点的前一个节点,找到后更改其next指针,问题是找该节点的前驱节点的方法就得从头节点开始遍历链表啊,这效率太低了,如果我们在每个节点中不仅能存储它的后继节点指针还能存储其前驱节点的指针就方便了,这就是双向链表的原理了。

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

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

相关文章

2024阿里云服务器ECS介绍_全方位解析_CPU性能详解

阿里云服务器ECS英文全程Elastic Compute Service&#xff0c;云服务器ECS是一种安全可靠、弹性可伸缩的云计算服务&#xff0c;阿里云提供多种云服务器ECS实例规格&#xff0c;如经济型e实例、通用算力型u1、ECS计算型c7、通用型g7、GPU实例等&#xff0c;阿里云百科aliyunbai…

Ubuntu 在线Swap扩容

1. 查看本机swap空间 free -h 2. 找一个较大的高速盘&#xff0c;创建swap的空间 mkdir /swap cd /swap sudo dd if/dev/zero ofswapfile bs50M count1k3.建swapfile&#xff0c;大小为bs*count 50M * 1k 50G 4.标记为Swap文件&#xff0c;让系统能识别交换文件。 sudo mk…

C1-3.2 关于‘神经网络’

C1-3.2 关于‘神经网络’ 【注释】 彩色图像&#xff08;RGB&#xff09;由三原色构成&#xff0c;二维图像在任意一个点像素为立体三层结构&#xff0c;分别是红色、绿色、蓝色值&#xff0c;该值的范围在0∽255之间 1、全连接神经网络——整体架构 【注释】&#xff1a; …

科技顶天,市场立地 。璞华科技“顶天立地”的成长之路

科技顶天&#xff0c;市场立地。 几十年来&#xff0c;我们越来越深刻地认识到&#xff0c;这就是真理&#xff0c;质朴而深刻。尤其在当前特殊的国际国内商业环境中&#xff0c;这一理念不但没有过时&#xff0c;反而恰逢其时。有这么一家企业&#xff0c;一直践行“科技顶天…

分类预测 | Matlab实现RP-LSTM-Attention递归图优化长短期记忆神经网络注意力机制的数据分类预测【24年新算法】

分类预测 | Matlab实现RP-LSTM-Attention递归图优化长短期记忆神经网络注意力机制的数据分类预测【24年新算法】 目录 分类预测 | Matlab实现RP-LSTM-Attention递归图优化长短期记忆神经网络注意力机制的数据分类预测【24年新算法】分类效果基本描述模型描述程序设计参考资料 分…

2023极客大挑战web小记

拿到题目提示post传参还以为是道签到题 刚开始直接把自己极客大挑战的username以及password怼上去&#xff0c;但是不对。看看F12&#xff0c;有提示。 当一个搜索蜘蛛访问一个站点时&#xff0c;它会首先检查该站点根目录下是否存在robots.txt&#xff0c;如果存在&#xff0c…

近视的孩子用什么灯?学生考研护眼台灯推荐

随着时代快速发展&#xff0c;2022年我国近视人数达到了7亿&#xff0c;呈现低龄化趋势&#xff0c;儿童及青少年人数占了53.8%。现在学业负担都很重&#xff0c;每个家长都不希望自己的孩子近视或加深近视了&#xff0c;都会想尽一切办法保护视力。而护眼台灯就成了家长购买台…

智能路由器中的 dns.he.net可使用自定义域名的免费 DDNS 服务配置方法

今天介绍的这个是可以使用自定义域名同时支持使用二级域名的免费DDNS服务 dns.he.net的动态DDNS服务的配置方法, 这个服务相对还是比较稳定的, 其配置也和其他的DDNS服务有些不太一样, 首先他的主机名: 这里需要设置为登录后分配的区域域名: ipv6.he.net 然后就是 DDNS 用户…

Git新手?这篇文章带你飞!基础操作一网打尽!

推荐阅读 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;一&#xff09; 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;二&#xff09; 文章目录 推荐阅读Git初识Git啥是版本控制系统&#xff1f;&#xff1f;集中式VS分布式 git使用…

录屏怎么打开?看这里,录制视频不费事!

随着科技的快速发展&#xff0c;录屏已经成为人们日常生活中经常使用的功能。无论是录制游戏视频、教程讲解&#xff0c;还是录制在线会议&#xff0c;录屏软件都发挥着重要作用。然而&#xff0c;很多用户并不知道录屏怎么打开&#xff0c;以及如何使用它们。本文将介绍两种常…

【书生·浦语】大模型实战营——第四课作业

教程文档&#xff1a;https://github.com/InternLM/tutorial/blob/main/xtuner/self.md 基础作业需要构建数据集&#xff0c;微调模型&#xff0c;让其明白自己的弟位&#xff08;OvO&#xff01;&#xff09; 微调环境准备 进入开发机后&#xff0c;先bash&#xff0c;再创…

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -投票帖子明细实现

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

应急管理蓝皮书 |《应急预案数字化建设现状和发展建议》下篇

导读 《应急预案数字化建设现状和发展建议》&#xff1a;297-313页 《中国应急管理发展报告》系列蓝皮书由中央党校&#xff08;国家行政学院&#xff09;应急管理培训中心&#xff08;中欧应急管理学院&#xff09;联合社会科学文献出版社研创出版&#xff0c;本着“权威前沿…

RT-Thread I/O设备模型

I/O设备模型 绝大部分的嵌入式系统都包括一些I/O&#xff08;Input/Output&#xff0c;输入/输出&#xff09;设备&#xff0c;例如仪器上的数据显示屏、工业设备上的串口通信、数据采集设备上用于保存数据的Flash或SD卡&#xff0c;以及网络设备的以太网接口等&#xff0c;都…

Linux 内核学习 3a - 如何查看虚拟内存和物理内存,以及虚拟内存和物理内存之间转换

/proc/iomem, ioremap(), mmap() The kernel manages device resources like registers as physical addresses(物理地址). These are the addresses in /proc/iomem. The physical address is not directly useful to a driver; it must use ioremap() to map the space and …

linux安装MySQL5.7(安装、开机自启、定时备份)

一、安装步骤 我喜欢安装在/usr/local/mysql目录下 #切换目录 cd /usr/local/ #下载文件 wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.38-linux-glibc2.12-x86_64.tar.gz #解压文件 tar -zxvf mysql-5.7.38-linux-glibc2.12-x86_64.tar.gz -C /usr/local …

【电路电子学】7天速通攻略+笔记

7天是 看视频记笔记刷题的总时长&#xff0c;时间紧迫的同学可以看情况进行缩减。个人认为做题&#xff0c;尤其是解析齐全的题最重要&#xff01; 我校所用教材 《电路与电子学基础》唐胜安 复习总流程 所用材料&#xff08;都可自行找到免费资源&#xff09; 视频知识点讲…

机器人持续学习基准LIBERO系列5——获取显示深度图

0.前置 机器人持续学习基准LIBERO系列1——基本介绍与安装测试机器人持续学习基准LIBERO系列2——路径与基准基本信息机器人持续学习基准LIBERO系列3——相机画面可视化及单步移动更新机器人持续学习基准LIBERO系列4——robosuite最基本demo 1.更改环境设置 LIBERO-master/l…

【降龙算法】基于QT插件机制实现一个机器视觉算法小框架

机器视觉行业有各种各样的拖拉拽框架&#xff0c;也叫做低代码平台&#xff0c;例如国内海康的VisionMaster&#xff1a; 一个机器视觉框架需要包含各种算法模块&#xff0c;日志窗口&#xff0c;图像显示窗口等等&#xff0c;【降龙算法】就是做了一个入门级的机器视觉算法框…

Java入门IDEA基础语法

1&#xff1a;Java入门 1.1 Java简介 Java是什么&#xff1a; Java是一门非常优秀的计算机语言 语言&#xff1a;人与人交流沟通的表达方式 计算机语言&#xff1a;人与计算机之间进行信息交流沟通的一种特殊语言 Java之父&#xff1a;詹姆斯高斯林&#xff08;James Gosli…