数据结构·双向链表

news2024/9/25 11:12:26

1. 双向链表的结构

        我们之前提到过,双向链表的全称是:带头双向循环链表。带头就是相当于一个“哨兵位”,用来标记链表的开始,它存储的数据是无效的,但是它将存储有效的前驱节点和后继节点的地址,带头链表的好处我们在上节例题中体会到了。

        下面展示双向链表的结构:

        每个节点中存放3个成员,分别是上一节点的地址,该节点保存的数据,和下一节点的地址。然后把它们带上头再循环串起来就完成了。

2. 双向链表的实现

        老规矩3个文件先创建好,然后再创建链表节点的结构体

                

        下面我们开始实现链表的各种功能

2.1 初始化、销毁和打印

2.1.1 初始化

        双向链表是带头的,或者说有哨兵位的,所以插入数据之前链表中必须先初始化一个哨兵位

        

2.1.2 销毁

        销毁很简单,边遍历链表边销毁就好了,注意销毁之前要把下一个节点的地址保存下来,还有就是不要忘记把哨兵位也销毁掉

        因为最后一句话涉及到修改哨兵位成空指针,所以参数要传二级指针

        

2.1.3 打印

        打印的话就从头节点的下一个节点开始扫描,直到看到头节点结束

        ​​​​​​​

2.2 链表的插入

        插入前先申请一个新节点

2.2.1 尾插

        双向链表的插入要比单链表简单很多,对于尾插来说,单链表我们还要遍历链表以找到尾节点,但是双向链表的尾节点就是头节点的上一个节点,所以不需要遍历了。

        同时添加新节点的时候我们不需要观察目前链表是否为空,因为链表初始化的时候就已经加好头节点了,所以链表一定不为空。

        还有就是因为头节点是固定存在的,所以我们在插入新节点的时候不可能涉及到改变头节点的地址,所以在传参的时候我们只需要传链表的头节点的一级指针就够了,这一点在头插的时候表现的更明显。

        但是在插入的时候要注意节点链接的顺序,先把新节点接进去,在拆开旧节点的链子,重新链接。

        ​​​​​​​        ​​​​​​​

2.2.2 头插

        头插的时候因为头节点是固定的,所以再头插的时候都统一插在头节点的后面,也就不用考虑改变头节点了,只需要断言一下有没有头节点就行

        

        

2.3 链表的删除

2.3.1 尾删

        删除的话我们传一个参数就可以了,然后要注意的是多断言了一个链表不为空,还有就是自己要想好删除的时候链子链接的顺序。

        

2.3.2 头删

        双向链表的头删跟尾删几乎没有区别,注意的点也一样

        

2.4 查找

        

2.5 在任意位置插入和删除

2.5.1 在pos位置之后插入数据

        这也没啥好说的,注意先把新节点接进去,在接别的链子,防止有点指针给整丢了

        

2.5.2 删除pos位置数据

        这个更简单,记得连好之后把pos free掉就好了

        

3. 结语

        最后,我们对比一下双向链表的逻辑看起来似乎比单链表难一点,但是实际写代码的时候反而比单链表方便许多,尤其是有头节点或者说哨兵位的加入,我们不需要考虑那么多判断,头节点是否要移动的情况。

        再说回链表和顺序表的区别,顺序表的缺点是如果要删除某个数据,就要移动一整个顺序表中的数据,效率很低。但是顺序表和链表还是有相互的长处,顺序表适用于那种元素高效存储和频繁访问的情况,而链表适用于任意位置插入和删除频繁的情况,总之,各有所长,大家在实际应用中灵活运用吧。

4. 完整代码

        本节的完整代码双手呈上:

        List.h

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

//定义节点结构
typedef int DataType;
typedef struct ListNode 
{
	int data;
	struct ListNode* prev;
	struct ListNode* next;

}LTNode;

//双向链表是带头的,或者说有哨兵位的
//所以插入数据之前链表中必须先初始化一个哨兵位
//初始化和销毁
void LTInit(LTNode** pphead);
void LTDestory(LTNode** pphead);

//双向链表的打印
void LTPrint(LTNode* phead);

//哨兵位不需要改变,所以不用传二级指针了
//尾插
void LTPushBack(LTNode* phead, DataType x);
//头插
void LTPushFront(LTNode* phead, DataType x);


//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);


//查找
LTNode* LTFind(LTNode* phead, DataType x);

//在任意位置插入和删除
//在pos位置之后插入数据
void LTInsert(LTNode* pos, DataType x);
//删除pos位置数据
void LTEraze(LTNode* pos);

        List.h

#include"List.h"

//初始化和销毁
//初始化
void LTInit(LTNode** pphead)
{
	*pphead = (LTNode*)malloc(sizeof(LTNode));
	if (*pphead == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	(*pphead)->data = -1;
	//因为目前只有一个节点
	//所以哨兵位的前面和后面一个节点都是他自己
	(*pphead)->prev = *pphead;
	(*pphead)->next = *pphead;
}

//销毁
void LTDestory(LTNode** pphead)
{
	assert(pphead);
	//链表不能为空
	assert(*pphead);
	
	LTNode* pcur = (*pphead)->next;
	while (pcur != *pphead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//链表中只有哨兵位了
	free(*pphead);
	*pphead = NULL;
}

 
//双向链表的打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}





//申请新节点
LTNode* LTBuyNode(DataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	newnode->data = x;
	newnode->prev = NULL;
	newnode->next = NULL;
	return newnode;
}


//尾插
void LTPushBack(LTNode* phead, DataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	//先处理newnode
	newnode->prev = phead->prev;
	newnode->next = phead;

	//再处理之前的连线
	phead->prev->next = newnode;
	phead->prev = newnode;
}

//头插
void LTPushFront(LTNode* phead, DataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	//先链接新节点
	newnode->prev = phead;
	newnode->next = phead->next;
	//再链接其他
	phead->next->prev = newnode;
	phead->next = newnode;
}



//尾删
void LTPopBack(LTNode* phead)
{
	//不能没有头节点,说明链表还没有初始化
	assert(phead);
	//不能只有头节点,说明链表为空
	assert(phead->next != phead);

	//先让头节点的链子栓到倒数第二个节点上
	phead->prev = phead->prev->prev;
	//释放倒数第一个节点
	free(phead->prev->next);
	//倒数第二个节点的下一个节点是头节点
	phead->prev->next = phead;
}

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	phead->next = phead->next->next;
	free(phead->next->prev);
	phead->next->prev = phead;
}




//查找
LTNode* LTFind(LTNode* phead, DataType x)
{
	assert(phead);
	assert(phead->next != phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没找到
	return NULL;
}




//在任意位置插入和删除
//在pos位置之后插入数据
void LTInsert(LTNode* pos, DataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	newnode->next->prev = newnode;
	pos->next = newnode;
}


//删除pos位置数据
void LTEraze(LTNode* pos)
{
	assert(pos);

	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
}

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

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

相关文章

【Linux】进程通信——管道

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;【LeetCode】winter vacation training 目录 &#x1f4cb;进程通信的目的&#x1f4cb;管道匿名管道pipe函数创…

【DeepLearning-9】YOLOv5模型网络结构中加入MobileViT模块

一、神经网络的前中后期 在神经网络中&#xff0c;特别是在深度卷积神经网络&#xff08;CNN&#xff09;中&#xff0c;“网络早期&#xff08;低层&#xff09;”、“网络中期&#xff08;中层&#xff09;”和“网络后期&#xff08;高层&#xff09;”通常指的是网络结构中…

sqli-labs靶场(1-6关)

1、第一关 测试id1 id1加一个引号报错&#xff0c;两个引号正常&#xff0c;应该是字符&#xff0c;还有回显 猜测字段长度 id1 order by 3 -- id1 order by 4 -- 字段长度为三&#xff0c;接下来确定位置&#xff1a;id1 and 12 union select 1,2,3 -- 查出库名,及版本号id1 …

用C语言实现贪吃蛇游戏!!!(破万字)

前言 大家好呀&#xff0c;我是Humble&#xff0c;不知不觉在CSND分享自己学过的C语言知识已经有三个多月了&#xff0c;从开始的C语言常见语法概念说到C语言的数据结构今天用C语言实现贪吃蛇已经有30余篇博客的内容&#xff0c;也希望这些内容可以帮助到各位正在阅读的小伙伴…

深度学习(6)--Keras项目详解

目录 一.项目介绍 二.项目流程详解 2.1.导入所需要的工具包 2.2.输入参数 2.3.获取图像路径并遍历读取数据 2.4.数据集的切分和标签转换 2.5.网络模型构建 2.6.绘制结果曲线并将结果保存到本地 三.完整代码 四.首次运行结果 五.学习率对结果的影响 六.Dropout操作…

N-141基于springboot,vue网上拍卖平台

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 系统分前后台&#xff0c;项目采用前后端分离 前端技术&#xff1a;vueelementUI 服务端技术&#xff1a;springbootmybatis-plusredi…

一张图区分Spring Task的3种模式

是的&#xff0c;只有一张图&#xff1a; fixedDelay 模式cron 模式fixedRate 模式

2024/1/28周报

文章目录 摘要Abstract文献阅读题目引言方法The ARIMA modelTime delay neural network (TDNN) modelLSTM and DLSTM model 评估准则实验数据描述实验结果 深度学习AttentionAttention思想公式步骤 Attention代码实现注意力机制seq2seq解码器Model验证 总结 摘要 本周阅读了一…

腾讯云幻兽帕鲁4核16G/8核32G/16核64G服务器配置价格表

腾讯云幻兽帕鲁服务器4核16G、8核32G和16核64G配置可选&#xff0c;4核16G14M带宽66元一个月、277元3个月&#xff0c;8核32G22M配置115元1个月、345元3个月&#xff0c;16核64G35M配置580元年1个月、1740元3个月、6960元一年&#xff0c;腾讯云百科txybk.com分享腾讯云幻兽帕鲁…

uniapp组件库fullScreen 压窗屏的适用方法

目录 #平台差异说明 #基本使用 #触发压窗屏 #定义压窗屏内容 #注意事项 所谓压窗屏&#xff0c;是指遮罩能盖住原生导航栏和底部tabbar栏的弹窗&#xff0c;一般用于在APP端弹出升级应用弹框&#xff0c;或者其他需要增强型弹窗的场景。 警告 由于uni-app的Bug&#xff0…

深度强化学习(王树森)笔记04

深度强化学习&#xff08;DRL&#xff09; 本文是学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。本文在ChatGPT辅助下完成。 参考链接 Deep Reinforcement Learning官方链接&#xff1a;https://github.com/wangshusen/DRL 源代码链接&#xff1a;https://github.c…

探索IOC和DI:解密Spring框架中的依赖注入魔法

IOC与DI的详细解析 IOC详解1 bean的声明2 组件扫描 DI详解 IOC详解 1 bean的声明 IOC控制反转&#xff0c;就是将对象的控制权交给Spring的IOC容器&#xff0c;由IOC容器创建及管理对象。IOC容器创建的对象称为bean对象。 要把某个对象交给IOC容器管理&#xff0c;需要在类上…

基于springboot+vue的在线教育系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目背景…

如何看待程序员抄代码还拿着高薪这一说法?

程序员的工资构成&#xff1a;会复制粘贴值1块&#xff0c;知道去哪复制值5K&#xff0c;知道粘贴在哪值10K&#xff0c;粘贴完了能跑起来值15 有人说&#xff1a;能带领一伙人复制粘贴值20k。 有人说&#xff1a;能写一个自动复制粘贴的系统值30k。 有人纳闷问到&#xff1a…

兄弟DCP-7057黑白激光多功能一体机加粉后清零方法

硒鼓加粉机器上清零&#xff0c;方法如下&#xff1a; 打开安装硒鼓的前盖。按“清除”键&#xff0c;显示“更换硒鼓”。不用管提示&#xff0c;接着按“启用Start”&#xff0c;再按“”&#xff0c;屏幕上显示“01”。继续按“”&#xff0c;直到屏幕上显示“11”。按“OK”…

【C/C++】C/C++编程——变量和常量

文章目录 变量变量的声明变量命名规则变量的类型 常量常量的定义与初始化字面量常量整型常量浮点型常量字符常量常量表达式&#xff08;constexpr&#xff09; 大家好&#xff0c;我是 shopeeai&#xff0c;也可以叫我虾皮&#xff0c;中科大菜鸟研究生。今天我们来一起来学习C…

软考之项目管理

一、考点分布 盈亏平衡分析&#xff08;※&#xff09;进度管理&#xff08;※※※&#xff09;软件质量管理&#xff08;※※&#xff09;软件配置管理&#xff08;※※&#xff09; 二、盈亏平衡分析 正常情况下&#xff0c;销售额固定成本可变成本税费利润 盈亏平衡下&#…

微信朋友圈新功能:多账号同步发圈,定时发圈!

​你是否会有这种烦恼 想要发布一条朋友圈&#xff0c;但是却因为忙着搞其他事情无暇顾及&#xff0c;甚至忘记了需要发布朋友圈这个事情&#xff1f; 有多个微信号想要同时为它们发布同一条内容的朋友圈&#xff0c;但又不想要分别登录进去进行操作&#xff1f; 你是否厌倦了每…

算法刷题:p1387 最大正方形

解题思路&#xff1a; 利用动态规划的思想设置一个标记数组flag[][]&#xff0c;flag[i][j]用来记录矩阵op[][]中以op[i][j]为右下角的子矩阵中最大的正方形边长&#xff0c;那么动态方程就是 flag[i][j]min(flag[i-1][j],min(flag[i-1][j-1],flag[i][j-1]))1;左侧和上方以及左…

Java基础—面向对象OOP—17类与对象(创建、构造器、创建对象时简单内存分析)

把握重点&#xff0c;重点已标注&#xff0c;这篇笔记分了4个章节&#xff0c;重点看二、三、四 一、整体思维--重点把握面向对象的本质和特点 1、面向对象编程OOP&#xff1a; Object-Oriented programming 2、面向过程与面向对象 面向过程&#xff1a;线性思维 面向对象…