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

文章目录

  • Ⅰ 概念及结构
  • Ⅱ 基本操作实现
    • 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.mfbz.cn/a/168229.html

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

相关文章

2023年【高处安装、维护、拆除】模拟考试题及高处安装、维护、拆除模拟考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年【高处安装、维护、拆除】模拟考试题及高处安装、维护、拆除模拟考试题库&#xff0c;包含高处安装、维护、拆除模拟考试题答案和解析及高处安装、维护、拆除模拟考试题库练习。安全生产模拟考试一点通结合国家…

链动2+1模式:创新营销引领白酒产业新潮流

在当今高度竞争的市场环境中&#xff0c;创新营销模式对于企业的发展至关重要。链动21模式作为一种独特的营销策略&#xff0c;将白酒产品与该模式相结合&#xff0c;充分发挥其优势&#xff0c;通过独特的身份晋升和奖励机制&#xff0c;快速建立销售渠道&#xff0c;提高用户…

MIUI查看当前手机电池容量

MIUI查看当前手机电池容量 1. 按如下步骤操作生成bug报告 2. 按如下操作解压bug报告 Last learned battery capacity

kernel32.dll丢失都有什么解决办法,帮助大家解决kernel32.dll丢失的问题

kernel32.dll丢失是电脑中常出现的情况&#xff0c;今天就想和大脚聊聊这个kernel32.dll 文件&#xff0c;这个文件它的功能是干什么的&#xff0c;如果电脑中kernel32.dll 丢失都有什么解决办法&#xff0c;帮助大家解决kernel32.dll丢失的问题&#xff0c;本篇文章给大家提供…

excel-gen.js 导出excel 功能

目录 概要 整体架构流程 html部分&#xff1a; js部分&#xff1a; json部分&#xff1a; 小结 概要 功能会使用到如下插件&#xff1a; jszip.min.js FileSaver.js jquery.min.js excel-gen.js highcharts.js exporting.js export_data.js 主要是highcharts图表…

C编译环境和预处理(非常详细,建议收藏)

C编译环境和预处理&#xff08;非常详细&#xff0c;建议收藏&#xff09; 一、程序的翻译环境和执行环境二、 详解编译链接2.1 翻译环境2.2 编译本身的几个阶段符号汇总、符号表、合并段表、符号表的合并和重定位分别是什么&#xff1f; 2.2 运行环境 三、预处理详解3.1 预定义…

安卓主板_MTK安卓一体机方案定制

安卓一体机主板集成多媒体解码、3G&#xff08;4G/5G可选&#xff09;模块&#xff0c;GPS&#xff0c;液晶驱动、WIFI、蓝牙、串口于一体&#xff0c;支持绝大部分当前流行的视频及图片格式解码。支持MIPI接口的1280*720分辨率的显示屏&#xff0c;最大支持1280*720P解码。大大…

海外代理IP如何找到靠谱的?

现在市面上有很多代理服务商&#xff0c;大家可以根据自己的需求选择一个适合自己业务的的IP代理服务商&#xff0c;现在也有一些免费的&#xff0c;但如果力求稳定安全&#xff0c;还是选择付费的。 这里提醒一句&#xff0c;在买代理IP时最好找这种可以免费试用的&#xff0…

Axure RP Pro 8 mac/win中文版:打造无限可能的原型设计工具

在如今的数字化时代&#xff0c;原型设计工具越来越受到设计师和产品经理们的重视。而Axure RP Pro8作为一款强大的原型设计工具&#xff0c;成为了众多专业人士的首选。 首先&#xff0c;Axure RP Pro8具备丰富的功能。它提供了多种交互元素和动画效果&#xff0c;使得用户可…

文件加密软件哪个好丨2023年最值得收藏的6款文件加密软件

文件加密软件哪个好&#xff1f; 在这个安全事件频发的时代&#xff0c;信息安全、文件安全已成为很多人关注的话题。不管是电脑还是手机&#xff0c;都需要重视文件加密这个话题。 那今天就推荐6款最值得收藏的文件加密软件&#xff0c;并分析其中的优缺点。 一、电脑加密软…

WhatsApp新营销全解:如何才能真正留住你的客户

WhatsApp营销这件事上&#xff0c;从获取线索、留存客户、成交转化到复购推荐的整个流程中&#xff0c;方方面面的因素影响着最终的转化效果。今天开始&#xff0c;我们会在公众号内新增WhatsApp新营销全解系列&#xff0c;结合前人踩过的坑和成功经验&#xff0c;来为大家说说…

苹果手机内嵌h5如何禁止全局弹性效果

简单模拟一个场景&#xff0c;这是一个商城的商品分类页面&#xff0c;是一个左右布局&#xff0c;左面是所有的分类&#xff0c;右面是展示这个分类的商品&#xff0c;这里为了简单就只写一个demo了。 <!DOCTYPE html> <html lang"en"><head><…

关于代码混淆,看这篇就够了

​ 代码混淆一.基本概念java的bytecode很容易通过JAD等反编译工具还原出源代码。这样势必不满足安全的定义。如何一定程度上保护需要防止被反编译的源代码呢&#xff1f;混淆&#xff08;obfuscate&#xff09;技术。注意&#xff1a;用obfuscate防盗版是根本不可能&#xff0c…

超长圆钢在线直线度检测 告别手工测量时代

圆钢的直线度指的是它的表面形状是否呈现出直线。直线度是圆钢的重要品质要求之一&#xff0c;与其物理性能密切相关。在工业制造中&#xff0c;如果圆钢的直线度不达标&#xff0c;就会影响其后续的加工和使用效果&#xff0c;严重时甚至会造成损失。 超长圆钢的检测&#xff…

预包装食品备案与食品经营许可证两者的关系

在食品行业中&#xff0c;预包装食品备案和食品经营许可证是两个重要的概念。它们之间存在一定的关系&#xff0c;但又不完全相同。本文将详细介绍两者的定义、区别和联系。 一、预包装食品备案 预包装食品备案&#xff0c;是指对预包装食品的生产者或进口商进行备案登记的一种…

SpringDoc基础配置和集成OAuth2登录认证教程

本期内容 学会通过注解和Java代码的方式添加SpringDoc配置。在swagger-ui提供的页面上提供OAuth2登录认证&#xff0c;在集成Security的情况下便捷获取access_token并在请求时按照OAuth2规范携带。 为什么集成OAuth2登录认证&#xff1f; 现在大部分教程是在swagger-ui页面添…

远程数据采集继电器RTU如何应用在智能电动汽车充电桩

远程数据采集继电器&#xff08;Remote Terminal Unit&#xff0c;RTU&#xff09;在智能电动汽车充电桩中的应用&#xff0c;可以为充电桩系统提供更高效、安全和可靠的远程监控与控制功能。下面将详细说明RTU在智能电动汽车充电桩中的应用。 远程监控功能&#xff1a; RTU可以…

一份WhatsApp矩阵账号营销模式全解,有你不知道的玩法吗?

将WhatsApp营销践行到底&#xff0c;是傲途针对海外Social营销一直在做的事。在WhatsApp全球营销范围越来越广泛、营销模式越来越深入的当下&#xff0c;我们也在实践中积累了一套比较系统而全面的差异化矩阵营销模式&#xff0c;帮助大中小不同类型企业获得了有价值的结果。 …

前后端黄金组合:Django+Vue+Element UI,助你构建完美平台!

这是一篇什么文章&#xff1f; 一篇你对测试开发工作感兴趣&#xff0c;想了解系统工作逻辑的文章。 一篇是你在开始动手搭建环境前需要了解各工具原理的文章。 这是一篇你真正开始前需要查阅的文章。 本文介绍了前后端工作原理&#xff0c;前后端搭建的流程、搭建过程中需…
最新文章