上集回顾:线性池中的任务队列
第一集:顺序存储队列
第二集:链式存储队列
第三集:线性池中的任务队列
观看本系列博文提醒:
你将学会队列的两种最基本的表现形式:顺序存储队列 和 链式存储队列;
一个扩展队列的使用方法:循环队列;
两个企业级队列的应用:线性池中的任务队列 和 优先链式存储队列。
队列的原理
队列是一种受限的线性表,(Queue),它是一种运算受限的线性表,先进先出(FIFO First In First Out).
例如上图中,圆球1先进,也是圆球1先出。
队列是一种受限的线性结构
- 它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。
- 生活中队列场景随处可见: 比如在电影院, 商场, 或者厕所排队。。。。。。
由上图我们可以知道,队列中有两个“指针”,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;
}
运行截图:
总结:
其实循环队列就是顺序储存队列的扩展版,基本操作都是一样的,算法难度不是很大!注意好队尾“指针”是如何回到队列的头部位置那些简单算法就行!加油!
注意:由于一篇博客内容太多,所以我将会把他分成几篇进行讲解!
祝各位学习愉快!
下集预告:
你将学会企业级队列的应用:优先链式存储队列;
请持续关注!