【数据结构初阶(3)】双向带头结点循环链表

news2024/11/16 23:51:45

文章目录

  • Ⅰ 概念及结构
  • Ⅱ 基本操作实现
    • 1. 结点的定义
    • 2. 创建头节点
    • 3. 创建新结点
    • 4. 双向链表销毁
    • 5. 双向链表打印
    • 6. 双向链表尾插
    • 7. 双向链表尾删
    • 8. 双向链表头插
    • 9. 双向链表头删
    • 10. 双向链表查找
    • 11. 在指定 pos 位置前插入新结点
    • 12. 删除指定 pos 位置的结点
  • Ⅲ 十分钟手搓链表

Ⅰ 概念及结构

  • 双向链表的每一个结点中不仅仅只有指向后继结点的 next 指针,还有指向其前趋结点的 prev 指针。
  • 双向链表的头节点的 prev 指针域指向链表的尾结点,双向链表的尾结点的 next 域指向链表的头结点,因此,在双向链表中不存在指向 NULL 的指针
  • 在带头结点的双向链表中,头节点不存储有效数据。因此,即使链表为空,双向链表中还要有一个头节点,头结点的前趋和后继指针都指向自己

在这里插入图片描述

Ⅱ 基本操作实现

1. 结点的定义

  • 双向循环链表的结点结构应该包含三部分:存储有效数据的数据域、存储前趋结点的前趋指针域、存储后继结点的后继指针域
typedef int LTDataType;		//数据域的数据类型

typedef struct ListNode		//双向链表结点
{
	LTDataType data;		//存储有效数据
	struct ListNode* prev;	//存储前趋结点
	struct ListNode* next;	//存储后继结点
}ListNode;

2. 创建头节点

  • 只有一个头结点时,链表是空的。
  • 头结点的前趋和后继都指针头节点本身。

在这里插入图片描述

代码实现

// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* head = (ListNode*)malloc(sizeof(ListNode));

	if (NULL == head)
	{
		perror("malloc");
		exit(-1);
	}

	head->prev = head;	//头结点的前趋指针指向自己
	head->next = head;	//头结点的后继指针指向自己

	return head;		//返回创建好的头结点
}

3. 创建新结点

实现方法

  • 创建除了头结点之外的新结点,这种结点存储有效数据。
  • 在创建新结点时,前趋和后继指针域都不指针任何结点,暂时都为 NULL。

在这里插入图片描述

代码实现

// 创建一个新结点
ListNode* BuySListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));

	if (NULL == newnode)
	{
		perror("malloc");
		exit(-1);
	}

	newnode->data = x;		//新结点数据域存储传来的数据
	newnode->next = NULL;	//新结点前趋指针暂时指向 NULL
	newnode->prev = NULL;	//新结点后继指针暂时指向 NULL

	return newnode;			//返回创建好的新结点
}

4. 双向链表销毁

实现方法

先定义一个 cur 指针指向头结点的后继结点,删除链表时有两种情况。

  1. 链表为空:此时头节点的后继结点就是头结点本身,直接释放头结点即可。
  2. 链表非空:使用一个指针 next 指向 cur 的下一个结点,然后删除 cur 指向的结点,再将 cur 指向 cur 的下一个结点,直到删的只剩头结点为止。

在这里插入图片描述

代码实现

// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);

	ListNode* cur = pHead->next;		//指向头结点的下一个结点
	
	while (cur != pHead)			
	{
		ListNode* next = cur->next;		//存储当前结点的下一个结点的地址
		free(cur);						//释放当前结点
		cur = next;						//让 cur 指向 cur 的下一个结点
	}

	free(pHead);						//不管链表开始是不是为空最后都会释放头结点
	pHead = NULL;
}

5. 双向链表打印

实现方法

  • 定义一个 cur 指针指向头节点的下一个结点 (head->next),输出 cur 指向的结点的数据域的内容,然后让 cur 指向下一个结点。
  • 只有在 cur 指针指向头结点的时候,打印才会结束。如果链表本身为空,则 cur 一开始就会指向头结点,自然什么都不会打印。

代码实现

// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);					//传过来的不是个空指针

	ListNode* cur = pHead->next;	//指向头结点的下一个结点(首结点)

	printf("头结点<->");
	while (cur != pHead)			//不指向头结点时说明链表中还有结点未遍历到
	{
		printf("%d<->", cur->data);	//输出结点数据域的内容
		cur = cur->next;			//指向当前结点的下一个结点
	}
	printf("\n");
}

6. 双向链表尾插

实现方法

在这里插入图片描述

代码实现

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);							//传过来的不是个空指针

	ListNode* newnode = BuySListNode(x);	//先创建要插入的新结点
	ListNode* tail = pHead->prev;			//找到双向链表的尾结点

	tail->next = newnode;					//尾结点的后继指针指向新结点
	newnode->prev = tail;					//新结点的前趋指针指向尾结点
	newnode->next = pHead;					//新结点的后继结点指向头结点
	pHead->prev = newnode;					//头结点的前趋指针指向新结点
}

7. 双向链表尾删

实现方法

在这里插入图片描述

代码实现

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead->prev);	//不是空链表

	ListNode* tail = pHead->prev;		//找到链表的尾结点
	ListNode* tail_prev = tail->prev;	//找到尾结点的前一个结点
	pHead->prev = tail_prev;			//让头结点的前趋指针指向尾结点的前一个结点
	tail_prev->next = pHead;			//让尾结点的前一个结点的后继指针指向头结点

	free(tail);							//删除尾结点
	tail = NULL;
}

8. 双向链表头插

实现方法

  • 定义一个 first 指针指向链表的首结点,之后就随便插入新结点了。
  • 因为已经用 first 指针保存了首结点的地址,所以不用担心会因为插入顺序导致出现 BUG。

在这里插入图片描述

代码实现

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListNode* newnode = BuySListNode(x);	//创建新结点
	ListNode* first = pHead->next;			//保存首结点地址

	pHead->next = newnode;					//头结点后继指向新结点
	newnode->prev = pHead;					//新结点前趋指向头结点
	newnode->next = first;					//新结点后继指向首结点
	first->prev = newnode;					//首结点前趋指向新结点
}

9. 双向链表头删

实现方法

  1. 定义一个 first 指针指向首结点,再定义一个 second 指针指向第二个结点。
  2. 让头结点后继指向第二个结点,让第二个结点前趋指向头结点。
  3. 最后即可删除 first 指向的首结点。

在这里插入图片描述

代码实现

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead->prev);	//链表不为空

	ListNode* first = pHead->next;		//指向首结点
	ListNode* second = first->next;		//指向第二个结点

	pHead->next = second;				//头结点的后继指针指向第二个结点
	second->prev = pHead;				//第二个结点的前趋指针指向头结点

	free(first);						//释放首结点
	first = NULL;
}

10. 双向链表查找

  • 使用一个 cur 指针遍历链表,返回第一个出现要查找的数据的结点的地址。

代码实现

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListNode* cur = pHead->next;	//链表不为空时指向首结点,链表为空时最后直接返回 NULL

	while (cur != pHead)			//链表还没彻底遍历一遍时继续指向循环体内容
	{
		if (cur->data == x)			//结点数据域等于要查找的数
		{
			return cur;				//返回出现要查找的数据的结点地址
		}

		cur = cur->next;
	}

	return NULL;
}

11. 在指定 pos 位置前插入新结点

获取 pos 位置

  • 利用双向链表查找找到要插入的 pos 位置。

实现方法

  • 定义一个 posprev 指针保存 pos 结点的前一个结点,之后可按照任意顺序插入新结点

在这里插入图片描述

代码实现

// 双向链表在 pos 的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);							//传过来的不是空指针
	
	ListNode* newnode = BuySListNode(x);	//创建新结点
	ListNode* posprev = pos->prev;			//保存 pos 的前一个结点

	posprev->next = newnode;				//前一个结点的 next 域指向新结点
	newnode->prev = posprev;				//新结点的 prev 域指向前一个结点
	newnode->next = pos;					//新结点的 next 域指向 pos 结点
	pos->prev = newnode;					//pos 的 prev 域指向新结点
}

功能特点

  • 如果 pos 是头结点的话,那么 pos 的前一个结点就是链表的尾结点,执行该函数就会变成对链表执行尾插

在这里插入图片描述

  • 如果 pos 是首结点的话,执行该函数功能就相当于直接对链表执行头插

在这里插入图片描述

12. 删除指定 pos 位置的结点

获取 pos 位置

  • 利用双向链表查找找到要删除的 pos 位置。

实现方法

  • 定义一个 posprev 指针指向 pos 位置的前一个结点,定义一个 posnext 指针指向 pos 位置的后一个结点。
  • 将 posprev 结点的后继指向 posnext,将 posnext 结点的前趋指向 posprev,最后再删除 pos 结点即可。

在这里插入图片描述

代码实现

// 双向链表删除 pos 位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	
	ListNode* posprev = pos->prev;	//保存 pos 位置结点的前趋结点
	ListNode* posnext = pos->next;	//保存 pos 位置结点的后继结点

	posprev->next = posnext;		//pos 前一个结点的后继指向 pos 的后一个结点
	posnext->prev = posprev;		//pos 后一个结点的前趋指向 pos 的前一个结点

	free(pos);
	pos = NULL;
}

功能特点

  • 如果 pos 是尾结点,该函数执行的功能就是尾删操作
  • 如果 pos 是首结点,该函数执行的功能就是头删操作

Ⅲ 十分钟手搓链表

  • 根据在指定 pos 位置前插入新结点删除指定 pos 位置的结点,这两个函数的功能特点可以直接对进行函数的头尾插和头尾删功能。
// 双向链表在 pos 的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);

// 双向链表删除 pos 位置的节点
void ListErase(ListNode* pos);

1. 指向头尾插功能

  • 头插:直接将 pos 定为首结点即可
ListInsert(pHead->next, xxx);	//首结点为 pos,执行的是头插
  • 尾插:直接将 pos 定为头结点即可
ListInsert(pHead, xxx);			//头结点为 pos,执行的是尾插

2. 执行头尾删功能

  • 头删:直接将 pos 定为首结点即可
ListErase(pHead->next)			//首结点为 pos,执行的是头删
  • 尾删:直接将 pos 定位尾结点即可
ListErase(pHead->prev)			//尾结点为 pos,执行的是尾删

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

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

相关文章

架构探索之路-第一站-clickhouse | 京东云技术团队

一、前言 架构, 软件开发中最熟悉不过的名词, 遍布在我们的日常开发工作中, 大到项目整体, 小到功能组件, 想要实现高性能、高扩展、高可用的目标都需要优秀架构理念辅助. 所以本人尝试编写架构系列文章, 去剖析市面上那些经典优秀的开源项目, 学习优秀的架构理念来积累架构设…

Adobe 2022,2023,2024永久安装包全家桶下载网盘下载和最全的安装教程!

收集整理&#xff1a;Adobe合集 最新:已更新到2024 资源包含&#xff1a;AE Adobe AE2022是一个非常强大的视频制作和后期制作软件&#xff0c;它可以让您制作出非常出色的电影特效、动画和其他非常优秀的视频作品。为了更好地使用这款软件&#xff0c;我们需要一些比较全面…

TeXLive 2023安装教程

TeXLive 2023安装教程 本文介绍最新TeX发行版——TeXLive 2023的安装步骤。如果你想用LaTeX进行写作&#xff0c;那么需要搭建LaTeX环境&#xff1a;可以选择下面两种方案之一进行安装&#xff1a;(1)TeXLive 2023TeXStudio或者(2)TeXLive 2023WinEdt 11。其中TeXLive 2023是由…

网络连接Android设备

参考&#xff1a;https://blog.csdn.net/qq_37858386/article/details/123755700 二、网络adb调试开启步骤 1、把Android平板或者手机WiFi连接到跟PC机子同一个网段的网络&#xff0c;在设置-系统-关于-状态 下面查看设备IP,然后查看PC是否可以ping通手机的设备的IP。 2、先…

智慧箱变动环辅控系统

智慧箱变动环辅控系统是一种集监控、管理、控制于一体的智能化系统。依托电易云-智慧电力物联网&#xff0c;以箱式变电站为管理对象&#xff0c;加装箱变网关&#xff0c;它主要用于对箱变环境进行实时监测和控制&#xff0c;以确保箱变的正常运行和安全性。 具体来说&#xf…

el-checkbox 对勾颜色调整

对勾默认是白色 改的时候一直在试着改color人&#xff0c;其实不对。我用的是element ui 的复选框 /* 对勾颜色调整 */ .el-checkbox__inner::after{/* 是改这里的颜色 */border: 2px solid #1F7DFD; border-left: 0;border-top: 0;}

DeepStream--测试lpdnet车牌检测模型

模型地址&#xff1a;https://catalog.ngc.nvidia.com/orgs/nvidia/teams/tao/models/lpdnet/version 模型格式已经从加密的etlt格式变为onnx格式。这个模型用于从汽车图片上检测出车牌位置&#xff0c;模型有两个&#xff0c;一个用于美国车&#xff0c;一个用于中国车。 Nv…

怎么批量提取文件名字到Excel中?

怎么批量提取文件名字到Excel中&#xff1f;Excel是由微软公司开发的一种电子表格软件&#xff0c;它是Microsoft Office办公套件的一部分。Excel提供了强大的数据处理和分析功能&#xff0c;用户可以使用Excel创建、编辑和管理电子表格&#xff0c;进行各种计算、数据分析、图…

如何评估千兆光模块和万兆光模块的性能及稳定性

在互联网高速发展的情况下&#xff0c;网络通信产品中的千兆光模块和万兆光模块成为了网络中必不可少的配件之一。千兆光模块和万兆光模块的性能及稳定性是网络稳定的重要因素&#xff0c;其性能的优劣关系着网络服务器和客户端的通信速度、质量及数据传输的可靠性。那么&#…

深度学习知识点

深度学习过程 data [] for i,d in enumerate(data):image,label d image,label image.cuda(),label.cuda()img net(image)optimizer.zero_grad()#需要将梯度信息清零&#xff0c;因为梯度计算是按照batch分批次计算的&#xff0c;如果这一批batch没清零&#xff0c;会影响…

外部 prometheus监控k8s集群资源

prometheus监控k8s集群资源 一&#xff0c;通过CADvisior 监控pod的资源状态1.1 授权外边用户可以访问prometheus接口。1.2 获取token保存1.3 配置prometheus.yml 启动并查看状态1.4 Grafana 导入仪表盘 二&#xff0c;通过kube-state-metrics 监控k8s资源状态2.1 部署 kube-st…

Enterprise Architect安装与使用

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl Enterprise Architect概述 官方网站&#xff1a;https://www.sparxsystems.cn/products/ea/&#xff1b;图示如下&#xff1a; Enterprise Architect是一个全功能的、基于…

【数据结构】HashMap 和 HashSet

目录 1.哈希表概念 2冲突 2.1概念 2.2 冲突-避免 2.3冲突-避免-哈希函数设计 2.4 冲突-避免-负载因子调节 ​编辑 2.5 冲突-解决-开散列/哈希桶 2.5冲突严重时的解决办法 3.实现 4.性能分析 5.与Java集合类的关系 1.哈希表概念 在顺序结构中&#xff0c;元素关键码和存…

你真的会写简历吗?软件测试简历修改包装...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、简历重要性以及…

执行npm的时候报权限问题的解决方案

我们在执行npm操作的过程中&#xff0c;会出现以下权限问题&#xff0c;解决方案: 管理员身份 运行cmd 切换目录到要执行命令的文件下 再进行npm操作即可

Java的IO流-序列化流

对象序列化&#xff1a;把Java对象写入到文件中去 package com.itheima.d3;import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream;public class Test1 {public static void main(String[] args) {try(//2、创建一个对象字节输出流…

服务器安全怎么保障,主机安全软件提供一站式保护

服务器主机安全是指保护服务器主机免受未经授权的访问、破坏、窃取或滥用。 现在如今大部分公司、单位的相关数据都是存储在云端服务器上&#xff0c;这样即方便查询也方便保存。 可是一旦服务器主机受到威胁&#xff0c;损失将会不可估计。 以下是一些服务器主机安全的建议…

springcloud整合seata我踩过的坑

版本问题 seata 1.5和1.5之前的目录结构不同,使用docker修改的配置文件也不同 1.4的左右 1.5之后docker 挂载文件也不同 1.5之前是使用自己写的挂载registry docker run -d -p 8091:8091 -p 7091:7091 --network newlead --name seata-serve -e SEATA_IP192.168.249.132…

基于纳什博弈的多微网主体电热双层共享策略(matlab代码)

目录 ​1 主要内容 2 部分代码 3 程序结果 4 下载链接 ​1 主要内容 该程序复现《Multi-Micro-Grid Main Body Electric Heating Double-Layer Sharing Strategy Based on Nash Game》模型&#xff0c;主要做的是构建基于纳什博弈的多微网主体电热双层共享模型&#xff0c;…

如何修改百科内容?百度百科内容怎么修改?

百科词条创建上去是相当不易的&#xff0c;同时修改也是如此&#xff0c;一般情况下&#xff0c;百科词条是不需要修改的&#xff0c;但是很多时候企业或是人物在近期收获了更多成就或是有更多的变动&#xff0c;这个时候就需要补充维护词条了&#xff0c;如何修改百科内容&…