[数据结构从小白到大牛]第五篇:3分钟带你吃透双链表并用C语言模拟实现

news2024/12/25 14:27:13

目录

1->前言

2->链表的概念和结构

2.1链表概念

2.2->带头双向循环链表结构

3->模拟实现带头双向循环链表 

3.1定义链表结点 struct ListNode

3.2创建链表结点 CreateLTNode 函数

3.3链表初始化函数 ListInit函数

3.4链表打印函数 ListPrint函数

3.5链表判空函数 ListEmpty函数 

3.6链表增删改查之尾部插入 ListPushBack 函数

3.7链表增删改查之头部插入 ListPushFront 函数

3.8链表增删改查之尾部删除 ListPopBack 函数

3.9链表增删改查之头部删除 ListPopFront 函数

3.10链表增删改查之查找数据 ListFind 函数

3.11链表增删改查之在pos位置之前插入

3.12链表增删改查之删除pos位置的值

3.13链表增删改查之销毁链表

4->您的专属鼓励师


1->前言

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

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

        上一个篇章我们讲解了无头单向非循环链表,这节我们讲解带头双向循环链表

2->链表的概念和结构

2.1链表概念

        概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的

2.2->带头双向循环链表结构

3->模拟实现带头双向循环链表 

3.1定义链表结点 struct ListNode

        数据域_data        指针域_prev指向前一个结点,_next指向后一个结点

#pragma once
//使用c语言模拟实现双向带头循环链表
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef struct ListNode
{
	struct ListNode* _prev;//指向前一个结点
	struct ListNode* _next;//指向后一个结点
	int _data;			   //存储数据
}LTNode;

3.2创建链表结点 CreateLTNode 函数

        注意:我们的结点动态申请内存在堆上,所以后边释放节点一定要free

//1.创建链表结点
LTNode* CreateLTNode(int num)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		return NULL;
	}
	//到这里申请空间成功
	newnode->_prev = NULL;
	newnode->_next = NULL;
	newnode->_data = num;
	return newnode;
}

3.3链表初始化函数 ListInit函数

        个人总结:任何数据结构或变量在使用之前一定要初始化给予初始值,让他处于一个明确的状态,否则会带来很多未知的错误,会浪费很多时间

//2.链表初始化
LTNode* ListInit()
{
	LTNode* phead = CreateLTNode(-1);//创建头结点

	phead->_prev = phead; //让头结点指向自己
	phead->_next = phead; //让头结点指向自己

	return phead;
}

3.4链表打印函数 ListPrint函数

        因为我们有头结点,头结点里的数据不算作有效数据不要打印,并且用头结点作为while循环的判断条件

//3.链表打印
void ListPrint(LTNode* phead)
{
	assert(phead);
	printf("head<==>");
	LTNode* cur = phead->_next;//从头结点下一个节点开始有效数据打印
	while (cur != phead)//cur不是头结点才能进循环
	{
		printf("%d<==>", cur->_data);
		cur = cur->_next;
	}
	printf("head\n");
}

3.5链表判空函数 ListEmpty函数 

//4.判断链表是否为空
//为空返回 true,不为空返回 false
bool ListEmpty(LTNode* phead)
{
	assert(phead);

	return phead->_next == phead;
}

3.6链表增删改查之尾部插入 ListPushBack 函数

//5.链表尾部插入
void ListPushBack(LTNode* phead, int num)
{
	assert(phead);

	//创建新结点并且找到最后一个结点
	LTNode* newnode = CreateLTNode(num);
	LTNode* tail = phead->_prev;

	//修改指针达到尾部插入效果
	tail->_next = newnode;
	newnode->_prev = tail;
	newnode->_next = phead;
	phead->_prev = newnode;
}

我们写代码测试下是否正确: 

//1.测试双链表尾部插入
void test1()
{
	LTNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);

	ListPrint(plist);
}

 看控制台输出:结果正确:

3.7链表增删改查之头部插入 ListPushFront 函数

//6.链表头部插入
void ListPushFront(LTNode* phead, int num)
{
	assert(phead);

	//创建新结点并且找到第一个结点
	LTNode* newnode = CreateLTNode(num);
	LTNode* next = phead->_next;

	//修改指针达到插入数据效果
	phead->_next = newnode;
	next->_prev = newnode;
	newnode->_prev = phead;
	newnode->_next = next;
}

我们写代码测试下是否正确:  

//2.测试双链表头部插入
void test2()
{
	LTNode* plist = ListInit();
	ListPushFront(plist, 1);
	ListPushFront(plist, 2);
	ListPushFront(plist, 3);
	ListPushFront(plist, 4);


	ListPrint(plist);
}

看控制台输出:结果正确:

3.8链表增删改查之尾部删除 ListPopBack 函数

//7.链表尾部删除
void ListPopBack(LTNode* phead)
{
	//判断链表存在并且有数据才能删除
	assert(phead);
	assert(!ListEmpty(phead));

	//找到最后一个结点和最后一个结点的前一个结点
	LTNode* tail = phead->_prev;
	LTNode* prev_tail = tail->_prev;

	free(tail);

	//修改指针
	prev_tail->_next = phead;
	phead->_prev = prev_tail;
}

 我们写代码测试下是否正确:  

//3.测试双链表尾部删除
void test3()
{
	LTNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPrint(plist);

	ListPopBack(plist);
	ListPopBack(plist);

	ListPrint(plist);

}

 看控制台输出:结果正确:

3.9链表增删改查之头部删除 ListPopFront 函数

//8.链表头部删除
void ListPopFront(LTNode* phead)
{
	//判断链表存在并且有数据才能删除
	assert(phead);
	assert(!ListEmpty(phead));

	//找到第一个结点和第二个结点
	LTNode* next = phead->_next;
	LTNode* next_next = next->_next;

	//释放头部结点
	free(next);
	//修改指针
	phead->_next = next_next;
	next_next->_prev = phead;
}

我们写代码测试下是否正确:  

//4.测试双链表头部删除
void test4()
{
	LTNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPrint(plist);

	ListPopFront(plist);
	ListPopFront(plist);


	ListPrint(plist);

}

 看控制台输出:结果正确:

 

3.10链表增删改查之查找数据 ListFind 函数

//9.链表查找数据,简单遍历就行
LTNode* ListFind(LTNode* phead, int num)
{
	assert(phead);
	LTNode* cur = phead->_next;
	while (cur != phead)
	{
		if (cur->_data == num)
			return cur;
		cur = cur->_next;
	}
	//到这里表示链表遍历一遍但是仍然没有找到数据,那就返回空
	return NULL;
}

 我们写代码测试下是否正确:  

//5.测试链表的查找数据
void test5()
{
	LTNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPrint(plist);

	LTNode* ptr = ListFind(plist, 3);
	printf("我查找的数据 : %d",ptr->_data);
}

  看控制台输出:结果正确:

3.11链表增删改查之在pos位置之前插入

//10.在pos之前插入
void LTInsert(LTNode* pos, int x)
{
	assert(pos);

	LTNode* prev = pos->_prev;
	LTNode* newnode = CreateLTNode(x);

	// prev newnode pos
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = pos;
	pos->_prev = newnode;
}

3.12链表增删改查之删除pos位置的值

//11.删除pos位置的值
void LTErase(LTNode* pos)
{
	assert(pos);

	LTNode* posPrev = pos->_prev;
	LTNode* posNext = pos->_next;

	posPrev->_next = posNext;
	posNext->_prev = posPrev;

	free(pos);
}

3.13链表增删改查之销毁链表

//12.销毁链表
void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->_next;
	while (cur != phead)
	{
		LTNode* next = cur->_next;
		free(cur);//销毁存储有效数据结点
		cur = next;
	}
	//销毁头结点
	free(phead);
}

4->您的专属鼓励师

        有些事情,你永远都没有办法做到“顶尖”,因为智力跟不上.但是所有的事情,你都可以做到“高段”,因为它需要的是时间的累积和精力的打磨.不聪明与聪明之间的区别,是很微妙的.有时候我们只会通过一次两次的结果,来判断整个人、整件事,其实这是不明智的.从小,邻居和亲戚在谈论我的时候,都会觉得我很聪明。但是只有我自己知道,我从来没有聪明过,只是看上去比较聪明而已.

        修行之路确实枯燥,但是我们把问题搞懂以后就发现他是那样的美妙!一遍学不会没关系吖,多看几遍,我也是学了好多遍呢,小伙伴们肯定学的又快又好!!!最后希望写的内容对小伙伴们有所帮助,我写的如果有哪里不对的地方请在评论区或者私信指出来哦!让我们一起进步吖,任何疑问包括心情不好都可以找我聊聊,我很乐意当你的倾听者吖.     

   

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

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

相关文章

前端通过nginx部署一个本地服务的方法

前端通过nginx部署一个本地服务的方法&#xff1a; 1.下载ngnix nginx 下载完成后解压缩后运行nginx.exe文件 2.打包你的前端项目文件 yarn build 把生成的dist文件复制出来&#xff0c;替换到nginx的html文件下 3.配置conf目录的nginx.conf文件 主要配置server监听 ser…

不同的浮点数类型

不同的浮点数类型 尽管4字节的浮点数可表达相当大的数值&#xff0c;但对于人类而言&#xff0c;总不够用。一般而言&#xff0c;浮点数有3种类型&#xff0c;单精度的float和双精度的double以及更长的long double, 可参考&#xff1a;数据类型大小 不同语言的浮点数类型 C/Obj…

蓝桥杯第21场小白入门赛补题

5.蓝桥派对 思路 &#xff1a;一个区间与多少个其他区间有关联&#xff0c;先对所有区间左端点和右端点从小到大排序&#xff0c;对于每个询问&#xff0c;我们先算出[1,r]这个区间里有多少个区间的起点即区间总数&#xff0c;使用upper_bound函数&#xff0c;然后使用lower_bo…

推荐一款功能强大的数据库开发管理工具:SQLite Expert Pro

SQLite Expert Professional是一个功能强大的工具&#xff0c;旨在简化SQLite3数据库的开发。 它是SQLite的一个功能丰富的管理和开发工具&#xff0c;旨在满足所有用户从编写简单SQL查询到开发复杂数据库的需求。 图形界面支持所有SQLite功能。 它包括一个可视化查询构建器&a…

sql专题 之 常用命令

文章目录 查询基础语法查询全表查询选择查询&#xff1a;常量和运算&#xff1a; 条件查询where运算符&#xff1a;、 !、<、>空值&#xff1a;null模糊查询&#xff1a;like逻辑运算&#xff1a;and or not 去重&#xff1a;distinct排序&#xff1a;order by截断和偏移…

Unity的gRPC使用之实现客户端

应用背景&#xff1a;本想Unity调用C的dll库获取一些数据资源&#xff0c;但是由于自己调用的C库模块化处理的不太理想&#xff0c;众多dll之间相互依赖&#xff0c;使得在调用dll的时候&#xff0c;会忽略一些dll的缺失&#xff0c;使Unity项目报错&#xff0c;故想到了使用gR…

Linux基础-常用操作命令详讲

Linux基础-常用操作命令详讲 一、openssl加密简单介绍 1. 生成加密的密码散列&#xff08;password hash&#xff09;​编辑 1.1 常见的选项总结表 1.2 加密参数详解 2. 自签名证书 3. 证书转换 二、文件管理 1. 创建空文件 ​编辑 2. 删除文件 4. 新建目录 ​编辑…

[大模型]视频生成-Sora简析

参考资料&#xff1a; Sora技术报告https://openai.com/index/video-generation-models-as-world-simulators/4分钟详细揭密&#xff01;Sora视频生成模型原理https://www.bilibili.com/video/BV1AW421K7Ut 一、概述 相较于Gen-2、Stable Diffusion、Pika等生成模型的前辈&am…

STM32学习笔记-外部中断和外部时钟

文章目录 EXTI基本结构AFIO 定时器1. STM32 定时器的种类2. 定时器的主要功能3. 定时器的配置4. 定时器 PWM 输出模式5. 定时器中断配置输出比较1. 输出比较模式概述2. 输出比较模式的配置今天实在有点疲惫了&#xff0c;明天继续学吧。 EXTI基本结构 AFIO 中断引脚选择&#…

【测试小白--如何写好测试用例--测试用例编写的方法+结合常见登录模块为实例--保姆级教学】

测试用例编写方法&登录模块实例 一、测试用例编写方法1. 等价类划分2. 边界值分析3. 状态转换测试4. 决策表测试5. 错误推测6. 用户场景测试7. 安全测试用例 二、登录模块测试用例实例1. 等价类划分2. 边界值分析3. 状态转换测试4. 决策表测试5. 错误推测6. 用户场景测试7.…

Python数据可视化seaborn

产品经理在做数据分析时可能需要通过可视化来分析。seaborn官网 1. relplot 散点图 https://seaborn.pydata.org/examples/scatterplot_sizes.html import pandas as pd import seaborn as sns df pd.DataFrame({x: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],y: [8, 6, 7, 8, 4, 6,…

Ubuntu Linux

背景 Ubuntu起源于南非&#xff0c;其名称“Ubuntu”来源于非洲南部祖鲁语或豪萨语&#xff0c;意为“人性”、“我的存在是因为大家的存在”&#xff0c;这体现了非洲传统的一种价值观。Ubuntu由南非计算机科学家马克沙特尔沃斯&#xff08;Mark Shuttleworth&#xff09;创办…

yolov8涨点系列之轻量化主干网络替换

文章目录 YOLOv8 替换成efficientvit轻量级主干网络的好处计算效率提升模型部署更便捷方便模型移植 模型可扩展性增强便于集成其他模块支持模型压缩技术 主干网络替换1.创建yolov8_efficeintVit.py2.修改task.py(1)引入创建的efficientViT文件(2)修改_predict_once函数(3)修改p…

碧桂园服务启动“乘梯无忧”专项行动 携手业主共筑电梯安全新未来

摘要&#xff1a;全国400城8000项目全面覆盖 电梯是当代社会不可或缺的垂直交通工具&#xff0c;电梯安全问题不仅关系到居民的日常生活&#xff0c;更关乎到他们的生命财产安全。随着生活节奏的加快&#xff0c;居民对电梯的运行效率也有了更高的要求和期待。 碧桂园服务在2…

应对AI与机器学习的安全与授权管理新挑战,CodeMeter不断创新引领保护方案

人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;技术正在快速发展&#xff0c;逐渐应用到全球各类主流系统、设备及关键应用场景中&#xff0c;尤其是在政府、商业和工业组织不断加深互联的情况下&#xff0c;AI和ML技术的影响日益广泛。虽然AI技术的…

【AI换装整合包及教程】OOTDiffusion: AI换装工具的革命性创新

引言 在当今这个数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;技术的发展日新月异&#xff0c;从最初的语音识别、图像识别到现在的自然语言处理&#xff0c;AI的应用范围不断扩大&#xff0c;深刻地改变了我们的生活方式和工作模式。特别是在时尚界&#xff0c;…

全面解析:网络协议及其应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 # 全面解析&#xff1a;网络协议及其应用 文章目录 网络协议概述定义发展历程主要优势 主要网络协议应用层协议传输层协议网络层…

零基础‘自外网到内网’渗透过程详细记录(cc123靶场)——下

细节较多&#xff0c;篇幅较大&#xff0c;分为上/下两部分发布在两篇文章内 另一部分详见下面文章 零基础‘自外网到内网’渗透过程详细记录(cc123靶场)——上https://blog.csdn.net/weixin_62808713/article/details/143572185 八、第二层数据库服务器权限获取 猜到新闻资…

参数跟丢了之JS生成器和包装器

如需转载请注明出处.欢迎小伙伴一起讨论技术. 逆向网址:aHR0cHM6Ly91bmlvbi5qZC5jb20vcHJvTWFuYWdlci9pbmRleD9wYWdlTm89MQ 跟踪接口:aHR0cHM6Ly9hcGkubS5qZC5jb20vYXBp 跟踪参数:h5st 本文目标:记录学习下自定义的生成器和包装器,不做具体的参数加密逻辑分析 直接启动器进…

【浪潮商城-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…