数据结构初阶(c语言)-双向链表

news2024/11/23 7:06:15

这里首先纠正上篇文章一个错误,链表的一个有效数据点应该称为结点而不是节点。

一,双向链表的概念与结构

1.1概念与结构示意图

         我们所说的双向链表全称为带头双向循环链表,也就是说此链表带有哨兵位结点(不存放任何数据的结点,且为头结点)。图示结构如下:

         注意:这里的“带头”跟前面我们说的“头结点”是两个概念,实际前面的在单链表阶段称呼不严谨。

1.2双向链表结构代码

typedef int LTNDataType;
typedef struct ListNode
{
	LTNDataType val;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

          prev为指向前一个结点的指针,next为指向后一个结点的指针,val为该结点中存储的数据。

二,实现双向链表的功能

2.1创建链表节点函数LTBuyNode

          由于我们的双向链表在没有存放任何数据时,还有一个哨兵位结点,所以我们需要先实现链表结点创建函数:

LTNode* LTBuyNode(LTNDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("buynode:");
		exit(1);
	}
	newnode->val = x;
	newnode->next = newnode->prev = newnode;
	return newnode;
}

          我们的双向链表为循环链表,所以在只有一个结点时,我们创建的新结点需要自己的两个前后指针均指向自己,以便实现我们的双向链表初始化。

2.2双向链表的初始化函数LTInit

         由于我们的哨兵位结点不存放有效数据,所以我们需要给初始结点直接返回一个存放数据为-1(无效数据)的结点,作为双向链表的初始结点:

LTNode* LTInit()
{
	LTNode* pcur = LTBuyNode(-1);
	return pcur;
}

 2.3双向链表的尾插函数LTPushBack

void LTPushBack(LTNode* phead, LTNDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	phead->prev->next = newnode;
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev = newnode;
}

         先创建一个新结点并用临时变量进行接收,下一步则是使尾节点的next指向我们的新结点,同时将新结点的prev指向先前的尾结点,接下来由于我们创建的新结点为当前的尾结点,所以我们需要让其next指针指向哨兵位结点,最后再使哨兵位结点的prev指向我们的新结点即完成我们的尾插函数。

2.4双向链表的头插函数LTPushStart

         这里需要注意,如果我们是直接将数据插到哨兵位前面,我们的方法实际上此时与尾插法无异,所以要实现头插,我们则需要将新结点直接插入到哨兵位结点的后面,从而实现头插:

void LTPushStart(LTNode* phead, LTNDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead;
	newnode->next = phead->next;
	phead->next = newnode;
	newnode->next->prev = newnode;
}

          为了插入数据时的方便,我们先改变新插入结点的next与prev,这样不会对当前链表的结构产生影响,接下来我们便改变哨兵位结点的next与原哨兵位结点的next结点的prev,使其均指向我们创建的新结点,实现我们的头插法。

2.5判空函数LTEmpty

         当我们的链表只剩下哨兵位结点时,我们判定此时链表不存放任何有效数据,又由于我们此时只有一个结点,所以我们直接判定当前哨兵位结点的prev是否指向它自己即可:

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return (phead->next == phead);
}

2.6尾删函数LTDeltBack

         在进行尾删之前,我们也需要对链表进行判空,然后我们需要先用临时变量去保存尾结点的地址,同时改变尾结点的前结点的next,使其指向哨兵位,然后改变哨兵位结点的prev使其指向我们当前尾结点的前一个结点,最后释放尾结点即可:

void LTDeltBack(LTNode* phead)
{
	assert(phead && !LTEmpty(phead));
	LTNode* pcur = phead->prev;
	phead->prev->prev->next = phead;
	phead->prev = phead->prev->prev;
	free(pcur);
	pcur = NULL;
}

2.7头删函数LTDeltStart

         与尾删函数类似,也需要创建临时变量去记住要释放结点的位置,然后接下来步骤的思想与尾删基本相同:

void LTDeltStart(LTNode* phead)
{
	assert(phead && !LTEmpty(phead));
	LTNode* pcur = phead->next;
	phead->next = phead->next->next;
	phead->next->next->prev = phead;
	free(pcur);
	pcur = NULL;
}

2.8寻找目标位置函数LTFind

          与单链表的寻找方法基本一致,不过要注意终止条件应该是遍历到哨兵位结点时停止:

LTNode* LTFind(LTNode* phead,LTNDataType x)
{
	assert(phead && !LTEmpty(phead));
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->val == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	printf("没有找到\n");
	return NULL;
}

2.9删除指定位置和向指定位置之后插入数据函数LTDeltDesBack与LTInserDesBack

         与单链表一样,但是这里向指定位置之前或之后插入数据方法一致,只是你实现之后插入后,如果想实现之前,只需要去用prev寻找上一结点即可:
LTDeltDesBack:

void LTDeltDesBack(LTNode* pos,LTNode* phead)
{
	assert(pos && (pos->next != phead));
	LTNode* pcur = pos->next;
	pos->next->next->prev = pos;
	pos->next = pos->next->next;
	free(pcur);
	pcur = NULL;
}

LTInserDesBack:

void LTInserDesBack(LTNode* pos,LTNDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = pos;
	newnode->next = pos->next;
	newnode->next->prev = newnode;
	pos->next = newnode;
}

2.10销毁链表函数DestoLTN

void DestoLTN(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	LTNode* pcur1 = NULL;
	while (pcur != phead)
	{
		pcur1 = pcur->next;
		free(pcur);
		pcur = pcur1;
	}
	free(phead);
}

三,顺序表与链表的对比

 

         当我们学习完顺序表与链表之后,下篇文章我们将介绍栈与队列的实现,在理解顺序表与链表功能的实现后,栈和队列的实现非常简单,仅仅只会有概念上不同的问题,我们下篇文章见。

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

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

相关文章

Oauth2协议的四种模式

B站视频 概念 Oauth2.0(Open Authorization) 一个关于授权的开放网络标准 允许用户授权第三方应用访问用户存储在其他服务提供者上的信息 不需要将用户名和密码提供给第三方应用 Oauth2中的各个角色 授权码模式 第一步 获取授权码 以上流程中的授…

产品经理NPDP好考吗?

NPDP是新产品开发专业人员的资格认证,对于希望在产品管理领域取得认可的专业人士来说,NPDP认证是一项重要的资格。 那么,产品经理考取NPDP资格认证究竟难不难呢? 首先,NPDP考试的难易程度取决于考生的背景和准备情况…

通信类IEEE会议——第四届通信技术与信息科技国际学术会议(ICCTIT 2024)

[IEEE 独立出版,中山大学主办,往届均已见刊检索] 第四届通信技术与信息科技国际学术会议(ICCTIT 2024) 2024 4th International Conference on Communication Technology and Information Technology 重要信息 大会官网&#xf…

C#调用OpenCvSharp实现图像的角点检测

角点检测用于获取图像特征,以支撑运动检测、目标识别、图像匹配等方面的应用。常用的角点检测算法包括Kitchen-Rosenfeld算法、Harris算法、KLT算法、SUSAN算法等,本文学习并测试Harris角点检测算法。   关于Harris算法的数学原理请见参考文献1的第18、…

解开基于大模型的Text2SQL的神秘面纱

你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益: 了解大厂经验拥有和大厂相匹配的技术等 希望看什么,评论或者私信告诉我! 文章目录 一…

【Unity国产化信创平台】虚拟机VMware Workstation Pro虚拟机下载安装

目录 一、虚拟机软件VMware Workstation Pro下载 二、虚拟机安装流程 1.傻瓜式安装 2.是否自动安装WHP 一、虚拟机软件VMware Workstation Pro下载 https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion 官网各种访问出错,下载界面总是…

linux禁用root

linux禁用root 1. 禁止普通用户切换到root1.1 sudo -i和sudo -s的区别1.2 sudo -i和直接登录root账号的区别1.3 禁止sudo -i切换root1.4 禁止su - root切换root 2. 禁止root远程登录2.1 ssh禁止root登录2.2 禁止远程桌面登录 本文主要介绍: 如何禁止普通用户切换到r…

划重点!PMP报考条件、报考步骤、考试内容、适合人群

参加PMP认证的好处,可以从几个方面来认识: 一、参加PMP认证与考试的过程,同时是一个系统学习和巩固项目管理知识的过程 二、参加PMP认证,您可以获得由PMI颁发的PMP证书 而拥有PMP认证表示你已经成为一个项目管理方面的专业人员…

基于微信小程序的健康饮食系统/健康饮食管理系统

摘 要 随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的小程序应运而生,各行各业相继进入信息管理时代&…

ST Stellar-E SR5E1 22KW OBC combo 3KW DC-DC汽车充电器解决方案

对于全球的环境保护意识抬头,全球的汽车产业慢慢步入电动化的时代,以减少碳排放。整车系统主要是由电池、电驱、电控的三电所构成,其中电池系统是整车的动力来源,而对电池充电的OBC系统更甚重要。一具高度安全性且高效的OBC系统&a…

a bean of type ‘org.redisson.api.RedissonClient Springboot 集成Redisson 配置

Springboot 集成 在导入redisson坐标之后 <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.17.5</version></dependency> 使用注解注入示例 Resource private RedissonClient re…

QML鼠标右键菜单

原理非常简单&#xff0c;先写个Menu&#xff0c;通过MouseArea来填充需要右键菜单的区域。然后在onClicked信号中&#xff0c;让Menu的x、y等于当前鼠标的x、y&#xff0c;最后用调用Menu的open()方法将菜单在该位置显示出来。 import QtQuick import QtQuick.Controls.Mater…

【JDY-10M】蓝牙MESH

目录 一、实物图 二、原理图 引脚定义 三、简介 基本原理 产品特点 产品应用范围 出厂默认配置 应用 1&#xff0c;可直接与手机进行连接&#xff0c;进行数据的传输和控制 2&#xff0c;可蓝牙与蓝牙之间进行连接&#xff0c;可进行MESH组网 四、尺寸 五、注意 源文件下载 可访…

基于Jeecgboot3.6.3的vue3版本的流程ProcessViewer的修改

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、因为之前ProcessViewer是vue2的组件版本&#xff0c;平时显示也还正常&#xff0c;但在历史记录的时候老是出现下面的问题。 就是第一次进去在panel点击流程图的时候不会出现&#xff0…

React开发者并不存在

根本就没有所谓的React开发者 — 永远不要这样称呼自己。 这是许多软件开发者犯的一个巨大错误&#xff0c;浪费了你大量时间。 专注于工具而非概念。忽视了大局。 React只是一个JavaScript工具。JavaScript只是一个计算工具。计算只是一个解决问题的工具。 当我刚开始编码时&a…

VulnHub:cengbox1

靶机下载地址&#xff0c;下载完成后&#xff0c;用VirtualBox打开靶机并修改网络为桥接即可搭建成功。 信息收集 主机发现和端口扫描 扫描攻击机&#xff08;192.168.31.218&#xff09;同网段存活主机确认目标机ip&#xff0c;并对目标机进行全面扫描。 nmap 192.168.31.…

【QT】事件分发器 事件过滤器

qt 系统 - 事件分发器 and 事件过滤器 一、事件分发器1. 事件分发器概念2. 事件分发器工作原理 二、事件过滤器 一、事件分发器 1. 事件分发器概念 在 Qt 中&#xff0c;事件分发器(Event Dispatcher) 是一个核心概念&#xff0c;用于处理 GUI 应用程序中的事件。事件分发器负…

Java黑色界面陪玩高端小程序源码陪练APP源码H5公众号源码电竞系统

&#x1f680;【电竞新纪元】解锁高端陪玩小程序源码 & 陪练APP秘籍&#xff0c;H5公众号全攻略&#xff01; &#x1f3ae; 开篇&#xff1a;电竞热潮下的新机遇 Hey游戏迷们&#xff01;随着电竞行业的蓬勃发展&#xff0c;你是否也想在这股浪潮中分得一杯羹&#xff1…

opencascade AIS_Manipulator源码学习

前言 AIS_Manipulator 是 OpenCASCADE 库中的一个类&#xff0c;用于在3D空间中对其他交互对象或一组对象进行局部变换。该类提供了直观的操控方式&#xff0c;使用户可以通过鼠标进行平移、缩放和旋转等操作。 详细功能 交互对象类&#xff0c;通过鼠标操控另一个交互对象…

新手网站利用好网站地图,提高网站的pr和权重

新手网站利用好网站地图&#xff0c;提高网站的pr和权重 它能引导蜘蛛更好的抓取收录我们网站的资源&#xff0c;从而提高网站的pr和权重。但网站地图的更新是个费神费力的工作&#xff0c;对于站长&#xff0c;尤其是精力有限的草根站长&#xff0c;怎样才能将有限的时间发挥出…