队列的链式存储结构与实现

news2024/12/24 21:09:42

前言

在前面我们学习了队列的概念与循环队列,我们知道了循环链表的队列长度事先就得确定好,但是实际中队列长度我们事先大多不知道,所以还是得研究一下动态的队列长度的队列储存与实现。

虽然顺序存储也可以通过realloc来实现扩容,但是顺序存储出队列不太好出——①如果队头位置定在下标0的位置,出队列的效率低;②如果每一次出队列队头位置+1,虽然效率高了,但是空间利用率低下。

所以对于动态的队列长度的队列储存与实现,我们用链式结构实现更优一些,下面我们就来学习队列的链式存储结构与实现。

一、队列的链式存储结构与实现

1、队列的链式存储结构

队列的链式存储结构,就是使用链表实现,可以简称为链队列。注意该链队列是受到限制的,要满足队列先进先出FIFO(first in first out)的原则。

链队列结构的分析:

①实现我们选择单链表,因为节省空间。

②为了方便先进先出的操作,我们将队头指针指向链队列的头结点,队尾指针指向终端结点(单链表我们为什么不定义一个尾指针呢——因为在单链表tail只能解决尾插,还不如不用)。如下图:

不带哨兵位

在这里插入图片描述
带哨兵位

在这里插入图片描述

③空队列时,不带哨兵位——head = tail = NULL;带哨兵位——head与tail指向哨兵位。

2、队列的链式存储实现

(1)(无哨兵位)链队列结构的类型

解读:

①链队列由一个个结点组成,所以需要定义链队列结点类型。

②为了方便先进先出的操作,所以定义队首指针与队尾指针。

③为了方便求队列有效数据个数,所以定义一个size储存个数(我们可以使用哨兵位来存储size吗——不可以,如当数据域为char时,129就溢出了。)。

④多个数据组成的复杂类型,最好将其定义为结构体(因为方便传参数,不管要改变结构体的多少成员,我们只需传结构体一个变量即可)。

代码示例:

//无哨兵位链队列的实现

//链队列元素类型的重命名(①方便更改;②见名知意)
typedef int QDataType;

//链队列结点类型
typedef struct QueueNode
{
	QDataType data;//数据域
	struct QueueNode* next;//指向后继结点
}QNode;

//链队列的结构类型
typedef struct Queue
{
	QNode* head;//队首指针——指向队头结点
	QNode* tail;//队尾指针——指向队尾结点
	int size;//保存有效数据个数——方便
}Queue;

(2)链队列的接口实现

0X00队列的初始化

解读: 首先结构体指针pq一定不为空,为了程序的健壮性断言一下。其次初始化队首指针、队尾指针与有效数据个数。

代码示例:

//初始化链队列
void QueueInit(Queue* pq)
{
	//断言pq不为空
	assert(pq);
	//链队列为空时
	pq->head = pq->tail = NULL;//不带哨兵位
	pq->size = 0;//空队列,有效数据个数为0
}

0X01队列的销毁

解读:

①断言pq不为空。

②在堆区申请的空间,如果不用记得自己释放,避免内存泄漏。

③链队列的销毁与链表的销毁一样,要从头结点逐个向后释放。注意释放cur结点之前要先保存它的后继结点,否则找不到。

④销毁结束之后,防止野指针将队首与队尾指针置为空。并且有效个数也为0。

代码示例:

//销毁链队列
void QueueDesttroy(Queue* pq)
{
	assert(pq);
	//保存队头位置
	QNode* cur = pq->head;
	//链队列的销毁与链表一样,要从头指针逐个销毁
	while (cur)
	{
		//保存cur的后继结点
		QNode* next = cur->next;
		//释放cur
		free(cur);
		//cur向后迭代
		cur = next;
	}
	//释放完记得将队首指针与队尾指针置为空
	pq->head = pq->tail = NULL;
	//有效数据个数为0
	pq->size = 0;
}

0X02入队列

解读:

①断言pq不为空。

②创建新节点,注意判断是否开辟成功,并且malloc之后记得初始化新节点。

③入队列,在队尾指针追加一个元素,即尾插。

④尾插注意分情况:情况1——队列为空,即头插,head与tail都要改变;情况2——队列不为空,即正常尾插,先链接再改变tail。

⑤入队列结束后,记得有效数据个数+1。

代码示例:

//入队列——队尾追加一个元素
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	//创建新的队列结点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	//判断是否开辟成功
	if (NULL == newnode)
	{
		//打印错误信息
		perror("malloc fail");
		return;
	}
	//malloc不会初始化,记得自己初始化新节点
	newnode->data = x;
	newnode->next = NULL;
	//入队列,即尾插
	//情况1:队列为空时,队首与队尾都要改变
	if (NULL == pq->head)
	{
		//head为空,tail也一定为空
		assert(pq->tail == NULL);
		//tail(head)指向newnode
		pq->tail = pq->head = newnode;
	}
	//情况2:队列不为空,只改变队尾
	else
	{
		//草图:tail   newnode(待插)
		pq->tail->next = newnode;
		//更新队尾位置
		pq->tail = newnode;
	}
	//入队之后有效数据个数+1
	pq->size++;
}

0X03出队列

解读:

①断言pq不为空。

②出队列,即删除队首元素,要断言队列不为空。

③删除队首元素注意tail是否要改变。情况1——只有一个结点时,释放之后head与tail都要置为空;情况2——有两个及以上结点——tail不会变,只改变head,注意要先保存head的后继结点,再释放头结点,最后链接。

④出队列之后,有效数据个数-1。

代码示例:

//出队列——队头删除一个元素
void QueuePop(Queue* pq)
{
	assert(pq);
	//断言队列不为空
	assert(!QueueEmpty(pq));
	//出队列,即头删
	//情况1:只有一个结点,删除后队首指针与队尾指针都要置为空
	if (NULL == pq->head->next)
	{
		//草图:head(待删)
		free(pq->head);
		//避免野指针,队首指针与队尾指针都要置为空
		pq->head = pq->tail = NULL;
	}
	//情况2:有两个及以上结点,删除后只改变队首指针
	else
	{
		//head(待删)   headnext
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	//出队列后,有效数据个数-1
	pq->size--;
}

0X04获取队列中的元素个数

解读:

①断言pq不为空。

②直接返回size即可,从这里就可以看出我们在队列结构中添加size成员的好处。

代码示例:

**//获取队列中的元素个数
int QueueSize(Queue* pq)
{
	assert(pq);
	//从这就能看出我们在队列成员中添加size的作用,直接返回size即可
	return pq->size;
}**

0X05检查队列是否为空

解读:

①断言pq不为空。

②有两种方式判断队列是否为空:方式1——有效数据个数为0,即表示队列为空;方式2——head与tail都为空,也表示队列为空。

代码示例:

//检查队列是否为空——为空返回真。
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	//size等于0,即队列为空
	return pq->size == 0;
	//队首指针与队尾指针为空,也表示队列为空
	//return (pq->head == NULL && pq->tail == NULL);
}

0X06获取队首元素

解读:

①断言pq不为空。

②断言队列不为空。

③直接返回队首元素pq->head->data即可。

代码示例:

//获取队首元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	//断言队列不为空
	assert(!QueueEmpty(pq));
	//返回队首元素
	return pq->head->data;
}

0X07获取队尾元素

解读:

①断言pq不为空。

②断言队列不为空。

③直接返回队尾元素pq->tail->data即可。

代码示例:

//获取队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	//断言队列不为空
	assert(!QueueEmpty(pq));
	//返回队尾元素
	return pq->tail->data;
}

总结:

①注意删除与获取队列元素时,都要判断队列不为空。

②入队列注意head是否要改变,出队列tail是否要改变。

③在入队列、出队列、销毁队列、初始化队列时队列的有效数据个数都有改变。

④free释放之后,指针的指向不会改变,防止野指针,记得free之后指针一般置为空等等。

二、总代码

1、Queue.h——声明头文件、队列结构、队列接口等

//头文件的声明
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

//无哨兵位链队列的实现

//链队列元素类型的重命名(①方便更改;②见名知意)
typedef int QDataType;

//链队列结点类型
typedef struct QueueNode
{
	QDataType data;//数据域
	struct QueueNode* next;//指向后继结点
}QNode;

//链队列的结构类型
typedef struct Queue
{
	QNode* head;//队首指针——指向队头结点
	QNode* tail;//队尾指针——指向队尾结点
	int size;//保存有效数据个数——方便
}Queue;

//链队列接口函数的声明

//初始化链队列
void QueueInit(Queue* pq);

//销毁链队列
void QueueDesttroy(Queue* pq);

//入队列——队尾追加一个元素
void QueuePush(Queue* pq, QDataType x);

//出队列——队头删除一个元素
void QueuePop(Queue* pq);

//获取队列中的元素个数
int QueueSize(Queue* pq);

//检查队列是否为空——为空返回真。
bool QueueEmpty(Queue* pq);

//获取队首元素
QDataType QueueFront(Queue* pq);

//获取队尾元素
QDataType QueueBack(Queue* pq);

2、Queue.c——接口的实现

#include"Queue.h"

//初始化链队列
void QueueInit(Queue* pq)
{
	//断言pq不为空
	assert(pq);
	//链队列为空时
	pq->head = pq->tail = NULL;//不带哨兵位
	pq->size = 0;//空队列,有效数据个数为0
}

//销毁链队列
void QueueDesttroy(Queue* pq)
{
	assert(pq);
	//保存队头位置
	QNode* cur = pq->head;
	//链队列的销毁与链表一样,要从头指针逐个销毁
	while (cur)
	{
		//保存cur的后继结点
		QNode* next = cur->next;
		//释放cur
		free(cur);
		//cur向后迭代
		cur = next;
	}
	//释放完记得将队首指针与队尾指针置为空
	pq->head = pq->tail = NULL;
	//有效数据个数为0
	pq->size = 0;
}

//入队列——队尾追加一个元素
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	//创建新的队列结点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	//判断是否开辟成功
	if (NULL == newnode)
	{
		//打印错误信息
		perror("malloc fail");
		return;
	}
	//malloc不会初始化,记得自己初始化新节点
	newnode->data = x;
	newnode->next = NULL;
	//入队列,即尾插
	//情况1:队列为空时,队首与队尾都要改变
	if (NULL == pq->head)
	{
		//head为空,tail也一定为空
		assert(pq->tail == NULL);
		//tail(head)指向newnode
		pq->tail = pq->head = newnode;
	}
	//情况2:队列不为空,只改变队尾
	else
	{
		//草图:tail   newnode(待插)
		pq->tail->next = newnode;
		//更新队尾位置
		pq->tail = newnode;
	}
	//入队之后有效数据个数+1
	pq->size++;
}

//出队列——队头删除一个元素
void QueuePop(Queue* pq)
{
	assert(pq);
	//断言队列不为空
	assert(!QueueEmpty(pq));
	//出队列,即头删
	//情况1:只有一个结点,删除后队首指针与队尾指针都要置为空
	if (NULL == pq->head->next)
	{
		//草图:head(待删)
		free(pq->head);
		//避免野指针,队首指针与队尾指针都要置为空
		pq->head = pq->tail = NULL;
	}
	//情况2:有两个及以上结点,删除后只改变队首指针
	else
	{
		//head(待删)   headnext
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	//出队列后,有效数据个数-1
	pq->size--;
}

//获取队列中的元素个数
int QueueSize(Queue* pq)
{
	assert(pq);
	//从这就能看出我们在队列成员中添加size的作用,直接返回size即可
	return pq->size;
}

//检查队列是否为空——为空返回真。
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	//size等于0,即队列为空
	return pq->size == 0;
	//队首指针与队尾指针为空,也表示队列为空
	//return (pq->head == NULL && pq->tail == NULL);
}

//获取队首元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	//断言队列不为空
	assert(!QueueEmpty(pq));
	//返回队首元素
	return pq->head->data;
}

//获取队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	//断言队列不为空
	assert(!QueueEmpty(pq));
	//返回队尾元素
	return pq->tail->data;
}

3、Test.c——测试

#include"Queue.h"

int main()
{
	//定义一个队列对象
	Queue pq;
	//初始化队列
	QueueInit(&pq);
	//入队列:1 2 3 4 5 
	QueuePush(&pq, 1);
	QueuePush(&pq, 2);
	QueuePush(&pq, 3);
	QueuePush(&pq, 4);
	QueuePush(&pq, 5);
	//打印队列的个数
	printf("%d\n", QueueSize(&pq));
	//出队列
	while (!QueueEmpty(&pq))
	{
		//打印队首元素
		printf("%d ", QueueFront(&pq));
		//出队列
		QueuePop(&pq);
	}
	//销毁队列
	QueueDesttroy(&pq);
	return 0;
}

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

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

相关文章

使用Postman快速复现浏览器的请求(包括生成调用代码)

前言 大家好&#xff01;我是sum墨&#xff0c;一个一线的底层码农&#xff0c;平时喜欢研究和思考一些技术相关的问题并整理成文&#xff0c;限于本人水平&#xff0c;如果文章和代码有表述不当之处&#xff0c;还请不吝赐教。 好久没有写开发类的工具使用文了&#xff0c;这…

Tigger绕过激活锁/屏幕锁隐藏工具,支持登入iCloud有消息通知,支持iOS12.0-14.8.1。

绕过激活锁工具Tigger可以用来帮助因为忘记自己的ID或者密码而导致iPhone/iPad无法激活的工具来绕过自己的iPhone/iPad。工具支持Windows和Mac。 工具支持的功能&#xff1a; 1.Hello界面两网/三网/无基带/乱码绕过&#xff0c;可以完美重启&#xff0c;支持iCloud登录、有消…

Leetcode—2.两数相加【中等】

2023每日刷题&#xff08;十五&#xff09; Leetcode—2.两数相加 迭代法实现代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l…

损坏的视频不能观看,还能修复吗?

3-1 在日常的生活或者工作中&#xff0c;特别是做摄像工作的人&#xff0c;有一定的概率会遇到损坏的视频文件&#xff0c;比如相机突然断电、无人机炸机等&#xff0c;都有可能导致保存的视频文件损坏。 如果遇到这种情况&#xff0c;该如何修复这种损坏的视频文件&#xff…

[架构之路-248/创业之路-79]:目标系统 - 纵向分层 - 企业信息化的呈现形态:常见企业信息化软件系统 - 供应链管理

目录 前言&#xff1a; 一、企业信息化的结果&#xff1a;常见企业信息化软件 1.1 供应链管理 1.1 什么是供应链与供应链管理What 1.2 为什么需要供应链管理系统Why&#xff1f; 1.3 谁需要供应链管理系统who&#xff1f; 1.4 供应链管理在企业管理中的位置where 1.5 什…

CS224W4.1——PageRank

在这篇中&#xff0c;我们将关注如何将图表示为矩阵&#xff0c;并讨论我们可以探索的后续属性。我们定义了PageRank的概念&#xff0c;进一步探索随机游走&#xff0c;并引入矩阵分解作为生成节点嵌入的视角。在第一部分&#xff0c;我们将介绍PageRank作为在图中对节点重要性…

mybarisplus插件(分页与乐观锁)

文章目录 1.分页插件2.自定义分页3.乐观锁3.1 场景3.2 乐观锁与悲观锁3.3 模拟修改冲突3.4 乐观锁解决问题 1.分页插件 MyBatis Plus自带分页插件&#xff0c;只要简单的配置即可实现分页功能 添加配置类MyBatisPlusConfig Configuration MapperScan("com.atguigu.mybatis…

Cordova插件开发二:高精度定位之卫星数据解析

文章目录 1.最终效果预览2.坐标获取方法3.在公共类中封装获取坐标的通用方法4.插件js中封装startGeoLocation方法5.插件主界面封装的方法1.最终效果预览 2.坐标获取方法 let obj = Object.assign({}, this.mapConfig.mapLocationObj)obj.isKeepCallBack = falselet res = await…

v免签易支付二开版源码+pc端订单监控+支付宝免挂机可回调

v免签二开版&#xff0c;又叫做v免签易支付版。它相当于是通过易支付的方式对接&#xff0c;不用单独搭建易支付系统了 安装教程 1、网站目录->运行目录 设置为public并保存 2、伪静态 设置为thinkphp并保存 3、打开网站目录 config/database.php &#xff0c;设置好您的m…

cdrx8和2020哪个版本更好用?有什么区别

经过多年的发展&#xff0c;cdr推出了很多优秀的版本&#xff0c;并顺应时代的发展更新了多项功能。随着cdr推出的软件版本增多&#xff0c;小伙伴们可选择的产品也在增多&#xff0c;那么该怎么选择呢&#xff1f;本文会给大家介绍cdrx8和2020的区别&#xff0c;CDRX8和2020哪…

大语言模型(LLM)综述(五):使用大型语言模型的主要方法

A Survey of Large Language Models 前言6 UTILIZATION6.1 In-Context Learning6.1.1 提示公式6.1.2 演示设计6.1.3 底层机制 6.2 Chain-of-Thought Prompting6.2.1 CoT的上下文学习6.2.2 关于CoT的进一步讨论 6.3 Planning for Complex Task Solving6.3.1 整体架构6.3.2 计划生…

【css3】涟漪动画

效果展示 dom代码 <div class"mapSelfTitle66"><div></div> </div> 样式代码 .mapSelfTitle66{width:120px;height:60px;position: relative;&>div{width:100%;height:100%;background: url("~/assets/images/video_show/err…

中国工科研究生200多篇英文论文中最常见的习惯(The Most Common Habits from more than 200 English Papers written by Gradua)

文章目录 中国工科研究生200多篇英文论文中最常见的习惯&#xff08;The Most Common Habits from more than 200 English Papers written by Graduate Chinese Engineering Students&#xff09;1 常见错误1.1 “a, an, the” 冠词的使用1.2 避免使用超过60个单词的长句1.3 通…

Project#1: Buffer Pool

文章目录 Task#1 - LRU-K Replacement PolicySizeRecordAccessSetEvictableEvictRemoveBugsTests Task#2 - Disk SchedulerStartWorkerThreadBUGs- [✅] BUG: std::abortTest Task#3 - Buffer Pool ManagerLearning NoteNewPageFetchPageUnpinPageDeletePageLearningNoteBugs- …

【OpenCV实现图像:用Python生成图像特效,报错ValueError: too many values to unpack (expected 3)】

文章目录 概要读入图像改变单个通道黑白特效颜色反转将图像拆分成四个子部分 概要 Python是一种功能强大的编程语言&#xff0c;也是图像处理领域中常用的工具之一。通过使用Python的图像处理库&#xff08;例如Pillow、OpenCV等&#xff09;&#xff0c;开发者可以实现各种各…

Python框架之Flask入门和视图

一、Flask入门和视图 需要安装Pycharm专业版 1. Flask简介 Python后端的2个主流框架 Flask 轻量级框架Django 重型框架 Flask是一个基于Python实现的web开发微框架 官方文档&#xff1a;https://flask.palletsprojects.com/ 中文文档&#xff1a;https://dormousehole.readthe…

【TES720D】青翼科技基于复旦微的FMQL20S400全国产化ARM核心模

板卡概述 TES720D是一款基于上海复旦微电子FMQL20S400的全国产化核心模块。该核心模块将复旦微的FMQL20S400&#xff08;兼容FMQL10S400&#xff09;的最小系统集成在了一个50*70mm的核心板上&#xff0c;可以作为一个核心模块&#xff0c;进行功能性扩展&#xff0c;特别是用…

每日自动化提交git

目前这个功能&#xff0c;有个前提&#xff1a; 这个git代码仓库&#xff0c;是一个人负责&#xff0c;所以不存在冲突问题 我这个仓库地址下载后的本地路径是&#xff1a;D:\Projects\Tasks 然后我在另外一个地方新建了一个bat文件&#xff1a; bat文件所在目录为&#xff1a…

高效改名,文件夹名称替换:一键批量替换文件夹名中间部分内容

在我们的日常生活和工作中&#xff0c;经常需要处理大量的文件夹&#xff0c;其中有些文件夹名称可能包含我们需要替换的内容。但如果我们一个一个地手动修改文件夹名称&#xff0c;不仅耗时而且容易出错。为了解决这个问题&#xff0c;我们可以使用云炫文件管理器高效的文件夹…