关于 C++ 队列算法,你该了解这些【第四集:循环队列】

上集回顾:线性池中的任务队列

第一集:顺序存储队列
第二集:链式存储队列
第三集:线性池中的任务队列


观看本系列博文提醒:
你将学会队列的两种最基本的表现形式:顺序存储队列链式存储队列
一个扩展队列的使用方法:循环队列
两个企业级队列的应用:线性池中的任务队列优先链式存储队列


队列的原理

队列是一种受限的线性表,(Queue),它是一种运算受限的线性表,先进先出(FIFO First In First Out).
在这里插入图片描述
例如上图中,圆球1先进,也是圆球1先出。

队列是一种受限的线性结构

  1. 它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。
  2. 生活中队列场景随处可见: 比如在电影院, 商场, 或者厕所排队。。。。。。
    在这里插入图片描述
    由上图我们可以知道,队列中有两个“指针”,front指向队首,rear指向队尾;
    至于length,他是整个队列的总长度(因为一个队列他是有固定长度的)。

循环队列

我们现在来回顾一下第一集中的内容。我们知道,顺序存储队列有两种出队的方式 。当我们采用第二种方式出队时,队列中的存储空间会越来越小直至内存都没耗光。这样可以避免元素移动,但是也带来了一个新的问题“假溢出”。
在这里插入图片描述
那么,能否利用前面的空间继续存储入队呢?
答案是可以的。所以我们有了现在的循环队列
让我们现在开始学习吧!


原理精讲

在开始码代码之前,我们得先了他的原理。

何为循环队列呢。就是当有元素出队后,可以重复利用前面已经出队的内存空间。在这里插入图片描述

如上图。队列之前出队的元素所占的空间可以重复利用。

所以我们可以重复的在之前的内存中插入元素,直到队列满为止。
当然使用循环队列,当队列满时,尾“指针”和头“指针”必须空开一个元素的位置,这样做有利于方便做队列已满的判断!如下图。
在这里插入图片描述
可能有朋友会问,那为什么不把头“指针”front 和 尾“指针”rear 相等时视为队列已满呢?
有这样的疑问是正常的,但是,是否发现,因为我们要把头“指针”front 和 尾“指针”rear 相等时作为队列空的情况,所以,队列满的情况还使用的话,就有冲突了!
在这里插入图片描述

好了,既然我们已经知道了循环队列的底层原理了,我们我们是不是就可以码代码了呢?
不急,还差一点。我们如何才能让队首队尾“指针”在到队列尾部时自动调转回到队列的头部呢?

其实有一个很简单的小算法可以搞定:
循环队列入队,队尾循环后移LQ->rear = (LQ->rear + 1) % QUEUE_MAX;
循环队列出队,队首循环后移LQ->front = (LQ->front + 1) % QUEUE_MAX;
为什么都要加1呢?是为了保持队首与队尾在队列满时保持一个元素的间距!
在这里插入图片描述

的队列:LQ->front == LQ->rear // 队首和队尾都指向同一个区块。
的队列:(LQ->rear + 1) % QUEUE_MAX == LQ->front // 因为队列满时,他们是空开一个元素的,所以,尾指针后移一位正好等于头指针。这也是为什么要空开一个元素的原因。

我们也还可以把循环队列比作一个大圆盘:
在这里插入图片描述

计算元素个数:
可以分两种情况判断:
如果LQ->rear >= LQ->front:元素的个数为:LQ->rear - LQ->front;
如果LQ->rear < LQ->front:元素的个数为:LQ->rear - LQ->front + QUEUE_MAX;

采用取模的方法可以把两种情况统一为:(LQ->rear - LQ->front + QUEUE_MAX) % QUEUE_MAX;

好了,原理讲完了,相信大家都已经非常的了解循环队列了。其实循环队列也不过如此嘛!下面我们一起来码代码!


定义

我们可以给队列的最大存储定义一个宏:
#define QUEUE_MAX 5 // 循环队列的最大存储个数

#define QUEUE_MAX 5		// 循环队列的最大存储个数

typedef int DateType;	// 循环队列存储的类型

typedef struct ArrayQueue {
	DateType queue[QUEUE_MAX];	// 存储循环队列元素的数组
	int front;	// 对头“指针”
	int rear;	// 队尾“指针”
}LoopQueue;

反正小编觉得就是和第一集中的循序存储队列一毛一样!


初始化循环队列

// 初始化循环队列
bool inItLoopQueue(LoopQueue* LQ) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return false;
	}

	LQ->front = LQ->rear = 0;

	return true;
}

在这里插入图片描述
头尾“指针”都赋值零。
反正小编觉得就是和第一集中的循序存储队列一毛一样!


判断循环队列是否为空

// 判断循环队列是否为空
bool estimateLoopQueueEmpty(LoopQueue* LQ) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return false;
	}

	if (LQ->front == LQ->rear) {	// 当头尾“指针”相等时,为空队列
		return true;
	}

	return false;
}

在这里插入图片描述
当头尾“指针”相等时,就是空队列!
程序员看图说话,不信的看上图!


判断循环队列是否已满

// 判断循环队列是否已满
bool estimateLoopQueueFull(LoopQueue* LQ) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return false;
	}

	if ((LQ->rear + 1) % QUEUE_MAX == LQ->front) {	// 队尾指针与对头指针相差一个元素的间隔时为满
		return true;
	}

	return false;
}

在这里插入图片描述
队尾“指针” 与 队首“指针”空开一个元素的间隔,所以判断条件可以将队尾“指针”加一后于队首“指针”作比较,当他们相等时,说明队列已满!
程序员看图说话,不信的看上图!


入队,将元素插入循环队列中

// 入队,将元素插入循环队列中
bool loopQueueInsert(LoopQueue* LQ, DateType date) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return false;
	}

	if (estimateLoopQueueFull(LQ)) {
		cout << "循环队列已满!" << endl;
		return false;
	}

	LQ->queue[LQ->rear] = date;	// 存值
	LQ->rear = (LQ->rear + 1) % QUEUE_MAX;	// 队尾指针加1
	
	return true;
}

在这里插入图片描述
当队列有元素出队时,前面的位置会空出来,我们的循环队列可以循环重复利用那里的内存,所以我们可以在前面插入!
插入也只是需要将队尾“指针”加一后与队列的最大存储进行与运算,均可得到队尾“指针”的下一个位置。


出队,将元素从队列中删除

// 出队
bool delteLoopQueue(LoopQueue* LQ, DateType* date) {	// 参数二:保存出队的元素返回
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return false;
	}

	if (!date) {
		cout << "date指针为NULL" << endl;
		return false;
	}

	if (estimateLoopQueueEmpty(LQ)) {
		cout << "循环队列为空,出队失败!" << endl;
		return false;
	}

	*date = LQ->queue[LQ->front];	// 获取出队的元素

	LQ->front = (LQ->front + 1) % QUEUE_MAX;	// 头指针加一

	return true;
}

在这里插入图片描述
出队也只是需要将队首“指针”加一后与队列的最大存储进行与运算,均可得到队首“指针”的下一个位置。


获取队首的元素

// 获取队首的元素
bool gainLoopQueueFrontValue(LoopQueue* LQ, DateType* date) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return false;
	}

	if (!date) {
		cout << "date指针为NULL" << endl;
		return false;
	}

	if (estimateLoopQueueEmpty(LQ)) {
		cout << "循环队列为空,获取失败!" << endl;
		return false;
	}

	*date = LQ->queue[LQ->front];	// 获取出队的元素

	return true;
}

简单合法性检查后,将头节点对应下标的元素赋值给date返回即可!


获取循环队列的元素个数

// 获取循环队列的元素个数
int size(LoopQueue* LQ) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return 0;
	}

	return (LQ->rear - LQ->front + QUEUE_MAX) % QUEUE_MAX;
}

代入原理精讲那条算法式子,就可以计算出当前队列的元素个数了!


输出队列中的元素

// 输出队列中的元素
void queuePrint(LoopQueue* LQ) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return;
	}

	if (estimateLoopQueueEmpty(LQ)) {
		cout << "循环队列为空!" << endl;
		return;
	}

	int i = LQ->front;

	while (i != LQ->rear) {
		cout << LQ->queue[i] << "\t";
		i = (i + 1) % QUEUE_MAX;
	}
	cout << endl;
}

这里我们需要定义整型变量 i 将头指针赋值给他,循环遍历当 i 不等于尾指针时,执行循环。循环体内输出当前 i 下标的元素后,将i + 1后与队列的最大存储作取余,就可以兼顾下图的情况了!
在这里插入图片描述


清空队列

//清空队列
void clearLoopQueue(LoopQueue* LQ)
{
	if (!LQ) return;
	LQ->front = LQ->rear = 0;
}

和初始化一毛一样,将头“指针”和尾“指针”头赋值0即可!


测试代码:

#include <iostream>
#include <Windows.h>

using namespace std;

#define QUEUE_MAX 5		// 循环队列的最大存储个数

typedef int DateType;	// 循环队列存储的类型

typedef struct ArrayQueue {
	DateType queue[QUEUE_MAX];	// 存储循环队列元素的数组
	int front;	// 对头“指针”
	int rear;	// 队尾“指针”
}LoopQueue;

// 初始化循环队列
bool inItLoopQueue(LoopQueue* LQ) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return false;
	}

	LQ->front = LQ->rear = 0;

	return true;
}

// 判断循环队列是否为空
bool estimateLoopQueueEmpty(LoopQueue* LQ) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return false;
	}

	if (LQ->front == LQ->rear) {	// 当头尾“指针”相等时,为空队列
		return true;
	}

	return false;
}

// 判断循环队列是否已满
bool estimateLoopQueueFull(LoopQueue* LQ) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return false;
	}

	if ((LQ->rear + 1) % QUEUE_MAX == LQ->front) {	// 队尾指针与对头指针相差一个元素的间隔时为满
		return true;
	}

	return false;
}

// 入队,将元素插入循环队列中
bool loopQueueInsert(LoopQueue* LQ, DateType date) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return false;
	}

	if (estimateLoopQueueFull(LQ)) {
		cout << "循环队列已满!" << endl;
		return false;
	}

	LQ->queue[LQ->rear] = date;	// 存值
	LQ->rear = (LQ->rear + 1) % QUEUE_MAX;	// 队尾指针加1
	
	return true;
}

// 出队
bool delteLoopQueue(LoopQueue* LQ, DateType* date) {	// 参数二:保存出队的元素返回
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return false;
	}

	if (!date) {
		cout << "date指针为NULL" << endl;
		return false;
	}

	if (estimateLoopQueueEmpty(LQ)) {
		cout << "循环队列为空,出队失败!" << endl;
		return false;
	}

	*date = LQ->queue[LQ->front];	// 获取出队的元素

	LQ->front = (LQ->front + 1) % QUEUE_MAX;	// 头指针加一

	return true;
}

// 获取队首的元素
bool gainLoopQueueFrontValue(LoopQueue* LQ, DateType* date) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return false;
	}

	if (!date) {
		cout << "date指针为NULL" << endl;
		return false;
	}

	if (estimateLoopQueueEmpty(LQ)) {
		cout << "循环队列为空,获取失败!" << endl;
		return false;
	}

	*date = LQ->queue[LQ->front];	// 获取出队的元素

	return true;
}

// 获取循环队列的元素个数
int size(LoopQueue* LQ) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return 0;
	}

	return (LQ->rear - LQ->front + QUEUE_MAX) % QUEUE_MAX;
}

// 输出队列中的元素
void queuePrint(LoopQueue* LQ) {
	if (!LQ) {
		cout << "循环队列不存在!" << endl;
		return;
	}

	if (estimateLoopQueueEmpty(LQ)) {
		cout << "循环队列为空!" << endl;
		return;
	}

	int i = LQ->front;

	while (i != LQ->rear) {
		cout << LQ->queue[i] << "\t";
		i = (i + 1) % QUEUE_MAX;
	}
	cout << endl;
}

//清空队列
void clearLoopQueue(LoopQueue* LQ)
{
	if (!LQ) return;
	LQ->front = LQ->rear = 0;
}

int main(void) {
	LoopQueue LQ;
	DateType date = 0;

	// 初始化
	inItLoopQueue(&LQ);

	// 入队
	for (int i = 0; i < 7; i++) {
		loopQueueInsert(&LQ, i);
	}
	cout << "队列中的元素个数:" << size(&LQ) << endl;
	queuePrint(&LQ);

	// 出队
	for (int i = 0; i < 2; i++) {
		if (delteLoopQueue(&LQ, &date)) {
			cout << "出队成功,出队的元素是:" << date << endl;
		}
	}
	cout << "队列中的元素个数:" << size(&LQ) << endl;
	queuePrint(&LQ);

	// 入队
	for (int i = 0; i < 3; i++) {
		loopQueueInsert(&LQ, i*3);
	}
	cout << "队列中的元素个数:" << size(&LQ) << endl;
	queuePrint(&LQ);

	clearLoopQueue(&LQ);

	system("pause");
	return 0;
}

运行截图:
在这里插入图片描述


总结:
其实循环队列就是顺序储存队列的扩展版,基本操作都是一样的,算法难度不是很大!注意好队尾“指针”是如何回到队列的头部位置那些简单算法就行!加油!

注意:由于一篇博客内容太多,所以我将会把他分成几篇进行讲解!

祝各位学习愉快!


下集预告:
你将学会企业级队列的应用:优先链式存储队列
请持续关注!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cpp_learners

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值