关于 C++ 队列算法,你该了解这些【第一集:顺序存储队列】

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


队列的原理

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

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

  1. 它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。
  2. 生活中队列场景随处可见: 比如在电影院, 商场, 或者厕所排队。。。。。。

在这里插入图片描述
由上图我们可以知道,队列中有两个“指针”,front指向队首,rear指向队尾;
至于length,他是整个队列的总长度(因为一个队列他是有固定长度的)。


顺序存储队列

采用数组来保存队列的元素,设立一个队首指针 front ,一个队尾指针 rear,分别指向队首和队尾元素。则 rear - front 即为存储的元素个数!

因为队列的长度是有限的,所以我们可以给队列的长度定义一个宏:
#define QUEUE_MAX 5 // 队列的最大存储个数

队列的定义

typedef int dateType;	// 队列存储的类型

typedef struct arrayQueue {
	dateType queue[QUEUE_MAX];	// 存储队列元素的数组
	int front;	// 队头“指针”
	int rear;	// 队尾“指针”
}seqQueue;

就是一个结构体,队列的储存是以数组方式存储的。
还定义了整型变量,front用于定位到队首元素,rear用于定位到队尾元素。


队列的初始化

// 队列初始化
void inItQueue(seqQueue* seq) {
	if (!seq) {	// 合法性检查
		return;
	}

	seq->front = seq->rear = 0;		// 对头与队尾“指针”都为零
}

在这里插入图片描述
初始化后他是一个空队列,所以两个“指针”都得为零。


判断队列是否为空

// 判断队列是否为空
bool estimateQueueEmpty(seqQueue* seq) {
	if (!seq) {	// 合法性检查
		cout << "队列不存在!";
		return false;
	}

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

	return false;
}

在这里插入图片描述
由上图我们可以知道,当队列为空时,“指针”front和rear都是指向0的,所以我们可以以此作为判断。


判断队列是否已满

// 判断队列是否已满
bool estimateQueueFull(seqQueue* seq) {
	if (!seq) {
		cout << "队列不存在!";
		return false;
	}

	if (seq->rear == QUEUE_MAX) {	// 当队尾“指针”等于队列最大存储个数时,为满
		return true;
	}

	return false;
}

在这里插入图片描述
先提前跟大家说一下,因为顺序存储队列是以数组方式存储的,最大下标是4,他的尾“指针”rear作为下标的话都是指向最后一个元素的下一个位置的,所以我们插入元素时,可以直接使用rear作为插入位置的下标。也就是说,当队列满时,他是与我们定义的数组长度是相等的,即我们定义的宏定义#define QUEUE_MAX 5,我们可以用rear与宏定义作比较,当他们相等时,说明队列已经满了。


入队,将元素插入队列中

// 入队,将元素插入队列中
bool enterQueue(seqQueue* seq, dateType date) {	// 参数二是入队的元素
	if (!seq) {
		cout << "队列不存在!";
		return false;
	}

	if (estimateQueueFull(seq)) {
		cout << "队列已满!插入 " << date << " 失败!" << endl;
		return false;
	}

	seq->queue[seq->rear] = date;	// 在队尾插入元素date
	seq->rear += 1;		// 队尾“指针”后移一位

	return true;
}

在这里插入图片描述
就如同我们上面所说的,当需要插入一个元素时,只需要将“指针”rear作为下标插入即可,然后再将rear加一,指向下一个空白的区域(如上图)。front“指针”永远都是指向头部第一个元素的。


出队,将元素从头部删除掉

他有两种出队方式:

  1. 将队列中队首的元素出队,后面的元素向前移动。优点:保证了队列的内存都能得到最大的利用,不浪费;缺点:假如队列中有成千上万个数据的话,删除一个元素造成后续那么多个元素的移动,会造成很大的开销,影响性能。
  2. 将队列中的队首元素出队,出队后对头“指针”front后移一位。优点:没有涉及到元素的移动,效率很快;缺点:由于他是移动头指针,所以每次出队,都会空置出一个内存区域,浪费内存。
情况一:将队列中队首的元素出队,后面的元素向前移动
// 出队一,将队列中队首的元素出队,后面的元素向前移动
bool deleteQueueFront_1(seqQueue* seq, dateType* date) {	// 参数二:保存出列的元素返回
	if (!seq) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "队列为空!" << endl;
		return false;
	}

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

	// 保存出列的元素返回
	*date = seq->queue[seq->front];

	// 从队头元素的下一个元素开始遍历,将后续的所有元素都往前移动一个位置
	for (int i = seq->front + 1; i < seq->rear; i++) {
		seq->queue[i - 1] = seq->queue[i];
	}
	seq->rear -= 1;

	return true;
}

在这里插入图片描述
从队头元素的下一个元素开始遍历,将后续的所有元素都往前移动一个位置。

上图就是出队前后的效果图。

可能有些朋友会有疑问,a1使出对了没错,但是为什么会有两个a3啊?
有这样的疑问是正常的。因为我们使用的删除方式是将后面的元素都往前移动一个位置,所以最后一个元素还是会保留在原来的位置的。但是,我们的尾“指针”rear最后自减了一,现在等于二,所以,就算是保留在哪里也不会出什么问题,因为我们根本访问不了他。
我们遍历队列最多只能访问前两个元素。

如果还是不懂的朋友可以把他理解成下图,就知道了。
在这里插入图片描述
准确来讲,还是第一幅图贴近现实!

情况二:将队列中的队首元素出队,出队后对头“指针”front后移一位
// 出队二,将队列中的队首元素出队,出对后对头“指针”front后移一位
bool deleteQueueFront_2(seqQueue* seq, dateType* date) {	// 参数二:保存出列的元素返回
	if (!seq) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "队列为空!" << endl;
		return false;
	}

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

	if (seq->front >= QUEUE_MAX) {
		cout << "队列已到尽头!" << endl;
		return false;
	}

	// 保存出列的元素返回
	*date = seq->queue[seq->front];

	seq->front += 1;	// 队首“指针”后移一位

	return true;
}

在这里插入图片描述

第二种方式要比第一种方式要多一次判断合法性检查。如果front“指针”已经大于或者等于队列的最大元素存储,那后面的操作就没有意义了。

如果合法性都正确,那么只需要将队首“指针”front自增一就行了。

如上图就是出队前和出队后的效果图。

也跟第一种删除方式差不多,现在是头元素保留在原来的位置,只是front“指针”只增一了,我们也就无法访问到原来的元素了。

不懂的朋友也可以把他理解成下图:
在这里插入图片描述
准确来讲,还是第一幅图贴近现实!


获取队列的首元素

// 获取队列的首元素
bool gainQueueFrontValue(seqQueue* seq, dateType* date) {
	if (!seq) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "队列为空!" << endl;
		return false;
	}

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

	// 保存出列的元素返回
	*date = seq->queue[seq->front];

	return true;
}

也就是和出队的代码差不多,合法性检查之后,就像元素赋值给date返回。


修改队列中任意位置的值

// 修改队列中任意位置的值
bool alterQueueValue(seqQueue* seq, int i, dateType date) {	// 参数二:修改位置;参数三:修改后的值
	if (!seq) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "队列为空!" << endl;
		return false;
	}

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

	if (i < seq->front || i >= seq->rear) {
		cout << "修改位置不合法!" << endl;
		return false;
	}

	for (int j = seq->front; j < seq->rear; j++) {
		if (i-1 == j) {
			seq->queue[j] = date;
			break;
		}
	}

	return true;
}

这个也是很简单的,从队首开始遍历,直到找到对应位置位置。将元素修改后break退出,然后结束函数。

注意:代码中,我们判断时对 i 进行了减一,我们不是从0开始数的,而是从1开始数。


获取队列中的元素个数

// 获取队列中的元素个数
int size(seqQueue* seq) {
	if (!seq) {
		cout << "队列不存在!" << endl;
		return false;
	}

	return seq->rear - seq->front;
}

我们可以将队尾“指针”rear减去队首“指针”front,就可以得到队列的元素个数了。


清空队列

// 清空队列
void clearQueue(seqQueue* seq) {
	if (!seq) {
		return ;
	}

	seq->front = seq->rear = 0;		// 对头与队尾“指针”都为零
}

清空队列和初始化队列都是一样的,将队首和队尾“指针”赋值0即可。


输出队列中的元素

// 输出队列中的元素
void queuePrint(seqQueue* seq) {
	if (!seq) {
		cout << "队列不存在!";
		return;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "队列为空!" << endl;
		return;
	}

	for (int i = seq->front; i < seq->rear; i++) {
		cout << seq->queue[i] << " ";
	}
	cout << endl;
}

测试代码:

#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;	// 队尾“指针”
}seqQueue;


// 队列初始化
void inItQueue(seqQueue* seq) {
	if (!seq) {
		return;
	}

	seq->front = seq->rear = 0;		// 对头与队尾“指针”都为零
}

// 判断队列是否为空
bool estimateQueueEmpty(seqQueue* seq) {
	if (!seq) {
		cout << "队列不存在!";
		return false;
	}

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

	return false;
}

// 判断队列是否已满
bool estimateQueueFull(seqQueue* seq) {
	if (!seq) {
		cout << "队列不存在!";
		return false;
	}

	if (seq->rear == QUEUE_MAX) {	// 当队尾“指针”等于队列最大存储个数时,为满
		return true;
	}

	return false;
}

// 入队,将元素插入队列中
bool enterQueue(seqQueue* seq, dateType date) {	// 参数二是入队的元素
	if (!seq) {
		cout << "队列不存在!";
		return false;
	}

	if (estimateQueueFull(seq)) {
		cout << "队列已满!插入 " << date << " 失败!" << endl;
		return false;
	}

	seq->queue[seq->rear] = date;	// 在队尾插入元素date
	seq->rear += 1;		// 队尾“指针”后移一位

	return true;
}

// 出队一,将队列中队首的元素出队,后面的元素向前移动
bool deleteQueueFront_1(seqQueue* seq, dateType* date) {	// 参数二:保存出列的元素返回
	if (!seq) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "队列为空!" << endl;
		return false;
	}

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

	// 保存出列的元素返回
	*date = seq->queue[seq->front];

	// 从头元素的下一个元素开始遍历,将后续的所有元素都往前移动一个位置
	for (int i = seq->front + 1; i < seq->rear; i++) {
		seq->queue[i - 1] = seq->queue[i];
	}
	seq->rear -= 1;

	return true;
}

// 出队二,将队列中的队首元素出队,出对后对头“指针”front后移一位
bool deleteQueueFront_2(seqQueue* seq, dateType* date) {	// 参数二:保存出列的元素返回
	if (!seq) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "队列为空!" << endl;
		return false;
	}

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

	if (seq->front >= QUEUE_MAX) {
		cout << "队列已到尽头!" << endl;
		return false;
	}

	// 保存出列的元素返回
	*date = seq->queue[seq->front];

	seq->front += 1;	// 队首“指针”后移一位

	return true;
}

// 获取队列的首元素
bool gainQueueFrontValue(seqQueue* seq, dateType* date) {
	if (!seq) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "队列为空!" << endl;
		return false;
	}

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

	// 保存出列的元素返回
	*date = seq->queue[seq->front];

	return true;
}

// 修改队列中任意位置的值
bool alterQueueValue(seqQueue* seq, int i, dateType date) {	// 参数二:修改位置;参数三:修改后的值
	if (!seq) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "队列为空!" << endl;
		return false;
	}

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

	if (i < seq->front || i >= seq->rear) {
		cout << "修改位置不合法!" << endl;
		return false;
	}

	for (int j = seq->front; j < seq->rear; j++) {
		if (i-1 == j) {
			seq->queue[j] = date;
			break;
		}
	}

	return true;
}

// 获取队列中的元素个数
int size(seqQueue* seq) {
	if (!seq) {
		cout << "队列不存在!" << endl;
		return false;
	}

	return seq->rear - seq->front;
}


// 输出队列中的元素
void queuePrint(seqQueue* seq) {
	if (!seq) {
		cout << "队列不存在!";
		return;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "队列为空!" << endl;
		return;
	}

	for (int i = seq->front; i < seq->rear; i++) {
		cout << seq->queue[i] << " ";
	}
	cout << endl;
}

// 清空队列
void clearQueue(seqQueue* seq) {
	if (!seq) {
		return ;
	}

	seq->front = seq->rear = 0;		// 对头与队尾“指针”都为零
}

int main(void) {
	seqQueue que;

	// 初始化队列
	inItQueue(&que);

	// 插入元素
	for (int i = 0; i < 6; i++) {
		enterQueue(&que, i * 5);
	}

	// 输出队列中的元素
	queuePrint(&que);

	// 元素出队1
	dateType date = 0;
	deleteQueueFront_1(&que, &date);
	cout << "出队的元素是:" << date << endl;
	cout << "出队元素后:" << endl;
	queuePrint(&que);

	cout << endl;

	// 元素出队2
	deleteQueueFront_2(&que, &date);
	cout << "出队的元素是:" << date << endl;
	cout << "出队元素后:" << endl;
	queuePrint(&que);

	cout << endl;

	/*for (int i = 0; i < 5; i++) {
		if (deleteQueueFront_2(&que, &date)) {
			cout << "出队的元素是:" << date << endl;
		} else {
			cout << "出队失败!" << endl;
		}
		
	}*/

	// 获取队首的元素
	gainQueueFrontValue(&que, &date);
	cout << "队首的元素是:" << date << endl;

	cout << endl;

	// 修改队列中任意位置的值
	if (alterQueueValue(&que, 2, 666)) {
		cout << "修改成功!" << endl;
	} else {
		cout << "修改失败!" << endl;
	}
	cout << "修改后:" << endl;
	queuePrint(&que);

	cout << endl;

	cout << "队列的元素个数是:" << size(&que) << endl;


	// 清空队列
	clearQueue(&que);

	system("pause");
	return 0;
}

总结:
顺序存储队列就跟数组一样,其操作都不难,只要注意下标就行!

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

祝各位学习愉快!


下集提醒:
你将学会队列的另一种最基本的表现形式:链式存储队列
请持续关注!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

cpp_learners

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

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

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

打赏作者

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

抵扣说明:

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

余额充值