循环队列作用
为充分利用向量空间,克服"假溢出"现象的方法。
循环队列的原理
在环状顺序表中,最后一个位置(a[6])和第一个位置(a[0])紧挨着,这样做的好处是:
- 随着元素做入队和出队操作,整个队列也只会在环状表中 “绕圈圈”,不存在队列整体向顺序表尾部移动的问题,顺序表的空间可以充分利用;
- 不存在“假溢出”的问题,当队列中的元素占满整个顺序表时,新元素入队会发生“真溢出”错误。
将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量。存储在其中的队列称为循环队列(Circular Queue)。循环队列是把顺序队列首尾相连,把存储队列元素的表从逻辑上看成一个环,成为循环队列。
循环队列就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用。在循环队列结构中,当存储空间的最后一个位置已被使用而再要进入队运算时,只需要存储空间的第一个位置空闲,便可将元素加入到第一个位置,即将存储空间的第一个位置作为队尾。 循环队列可以更简单防止伪溢出的发生,但队列大小是固定的。
在循环队列中,当队列为空时,有front=rear,而当所有队列空间全占满时,也有front=rear。为了区别这两种情况,规定循环队列最多只能有MaxSize-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。因此,队列判空的条件是front=rear,而队列判满的条件是front=(rear+1)%MaxSize。
循环队列实现入队操作
循环队列实现入队操作的过程和顺序队列类似,完成以下两步操作即可:
- 将新元素添加到 rear 指向的空闲空间;
- rear 向后移动一位,指向下一个空闲空间,为下次入队新元素做准备。
例如,在图 1 的基础上,向队列中添加一个新元素 5,实现过程如下图所示:
可以看到,当顺序表还有空闲空间时,由于我们将它想象成“首尾相连”的状态, a[6] 和 a[0] 紧挨着,rear 变量向后移动一位,会指向 a[0] 的位置。这意味着,队列左侧的空闲空间可以再次利用起来。
需要注意的是,循环队列判断“已满”的方式比较特殊。当我们根据图 2 的方法尝试将元素 6、7 分别入队时,最终的存储状态会变成下图所示:
在图 1 中,我们可以用toprear作为空队列的判断标志,但图 3 中队列已满的状态也是toprear,明显它们是冲突的。解决冲突常用的方法是:仍用toprear作为队列为空的判断标志,将队列已满的判断方法改为(rear+1)%MAX_LENtop,其中 MAX_LEN 为顺序表(数组)的长度。
例如,在图 2 的基础尝试将元素 6 入队,此时 rear 的值为 0,(rear+1)%MAX_LEN的值为 1,而 top 的值为 2,所以等式不成立,意味着循环队列未满,元素 6 就成功存储到了 a[0] 处。
在图 4 的基础上尝试将元素 7 入队,此时 rear 的值为 1,(rear+1)%MAX_LEN的值为 2,而 top 的值为 2,所以等式成立,意味着循环队列已满,元素 7 就无法入队。
有读者可能会问,图 4 的顺序表中明明还有一块空闲空间没有利用呢?是的,这就是循环队列判断“已满”的方法,浪费一块存储空间,避免和“队列为空”的状态发生冲突。
循环队列实现入队的 C 语言代码为:
int enQueue(int* a, int top, int rear, int data) {
//添加判断语句,如果rear超过max,则直接将其从a[0]重新开始存储,如果rear+1和top重合,则表示顺序表已满
if ((rear + 1) % MAX_LEN == top) {
printf("空间已满\n");
return rear;
}
//将新元素入队
a[rear % MAX_LEN] = data;
printf("元素 %d 成功入队\n", data);
//rear记录下一个空闲空间的位置
rear = (rear + 1) % MAX_LEN;
return rear;
}
程序中并没有直接将 data 元素存储到 a[rear] 中,而是将其存储到 a[rear%MAX_LEN] 中,这样就可以将顺序表当做环状表来用。
循环队列实现出队操作
循环队列实现出队的过程也和顺序队列类似,依次执行以下两步操作:
- 将 top 记录的队头元素出队;
- 将 top 向后移动一位,记录新队头元素的位置。
前面已经讲过,循环队列判断“队列为空”的标志是 top==rear,因此循环队列实现出队操作的 C 语言代码为:
int deQueue(int* a, int top, int rear) {
//如果top==rear,表示队列为空
if (top == rear) {
printf("队列为空\n");
return top;
}
printf("元素 %d 成功出队\n", a[top]);
//top向后移动一个位置,记录新的队头
top = (top + 1) % MAX_LEN;
return top;
}
循环队列的完整实现程序
#include <stdio.h>
#define MAX_LEN 5//表示顺序表申请的空间大小
int enQueue(int* a, int top, int rear, int data) {
//添加判断语句,如果rear超过max,则直接将其从a[0]重新开始存储,如果rear+1和top重合,则表示顺序表已满
if ((rear + 1) % MAX_LEN == top) {
printf("空间已满\n");
return rear;
}
//将新元素入队
a[rear % MAX_LEN] = data;
printf("元素 %d 成功入队\n", data);
//rear记录下一个空闲空间的位置
rear = (rear + 1) % MAX_LEN;
return rear;
}
int deQueue(int* a, int top, int rear) {
//如果top==rear,表示队列为空
if (top == rear) {
printf("队列为空\n");
return top;
}
printf("元素 %d 成功出队\n", a[top]);
//top向后移动一个位置,记录新的队头
top = (top + 1) % MAX_LEN;
return top;
}
int main() {
//定义长度为 5 的顺序表
int a[MAX_LEN] = {0};
//当队列中没有元素时,队头和队尾指向同一位置
int top = 0, rear = 0;
//元素 1 成功入队
rear = enQueue(a, top, rear, 1);
//元素 2 成功入队
rear = enQueue(a, top, rear, 2);
//元素 3 成功入队
rear = enQueue(a, top, rear, 3);
//元素 4 成功入队
rear = enQueue(a, top, rear, 4);
//元素 5 入队会失败
rear = enQueue(a, top, rear, 5);
//元素 1 成功出队
top = deQueue(a, top, rear);
//元素 5 再入队,会成功
rear = enQueue(a, top, rear, 5);
//元素 2 成功出队
top = deQueue(a, top, rear);
//元素 3 成功出队
top = deQueue(a, top, rear);
//元素 4 成功出队
top = deQueue(a, top, rear);
//元素 5 成功出队
top = deQueue(a, top, rear);
//队列为空时,出队操作失败
top = deQueue(a, top, rear);
return 0;
}
运行结构为:
元素 1 成功入队
元素 2 成功入队
元素 3 成功入队
元素 4 成功入队
空间已满
元素 1 成功出队
元素 5 成功入队
元素 2 成功出队
元素 3 成功出队
元素 4 成功出队
元素 5 成功出队
队列为空
总结
// 队列的顺序存储结构(循环队列)
#define MAX_QSIZE 5 // 最大队列长度+1
typedef struct {
int *base; // 初始化的动态分配存储空间
int front; // 头指针,若队列不空,指向队列头元素
int rear; // 尾指针,若队列不空,指向队列尾元素的下一个位置
} SqQueue;
// 构造一个空队列Q
SqQueue* Q_Init() {
SqQueue *Q = (SqQueue*)malloc(sizeof(SqQueue));
// 存储分配失败
if (!Q){
exit(OVERFLOW);
}
Q->base = (int *)malloc(MAX_QSIZE * sizeof(int));
// 存储分配失败
if (!Q->base){
exit(OVERFLOW);
}
Q->front = Q->rear = 0;
return Q;
}
// 销毁队列Q,Q不再存在
void Q_Destroy(SqQueue *Q) {
if (Q->base)
free(Q->base);
Q->base = NULL;
Q->front = Q->rear = 0;
free(Q);
}
// 将Q清为空队列
void Q_Clear(SqQueue *Q) {
Q->front = Q->rear = 0;
}
// 若队列Q为空队列,则返回1;否则返回-1
int Q_Empty(SqQueue Q) {
if (Q.front == Q.rear) // 队列空的标志
return 1;
else
return -1;
}
// 返回Q的元素个数,即队列的长度
int Q_Length(SqQueue Q) {
return (Q.rear - Q.front + MAX_QSIZE) % MAX_QSIZE;
}
// 若队列不空,则用e返回Q的队头元素,并返回OK;否则返回ERROR
int Q_GetHead(SqQueue Q, int &e) {
if (Q.front == Q.rear) // 队列空
return -1;
e = Q.base[Q.front];
return 1;
}
// 打印队列中的内容
void Q_Print(SqQueue Q) {
int p = Q.front;
while (Q.rear != p) {
cout << Q.base[p] << endl;
p = (p + 1) % MAX_QSIZE;
}
}
// 插入元素e为Q的新的队尾元素
int Q_Put(SqQueue *Q, int e) {
if ((Q->rear + 1) % MAX_QSIZE == Q->front) // 队列满
return -1;
Q->base[Q->rear] = e;
Q->rear = (Q->rear + 1) % MAX_QSIZE;
return 1;
}
// 若队列不空,则删除Q的队头元素,用e返回其值,并返回1;否则返回-1
int Q_Poll(SqQueue *Q, int &e) {
if (Q->front == Q->rear) // 队列空
return -1;
e = Q->base[Q->front];
Q->front = (Q->front + 1) % MAX_QSIZE;
return 1;
}