为了解决顺序队列“假溢出”的缺陷,所以引入了循环队列。
关于顺序队列请参考:顺序队列。
循环队列就是将顺序队列臆造成一个环状的空间(实际上不是,只是把它看成是环状的),即把存储队列元素的顺序表从逻辑上视为一个环。当队头指针 queue.front==MAXSIZE-1
时(即到数组的最后一个位置了),再前进一个位置就自动到 0,这可以利用除法取余运算(%
)来实现。
通常采用数组来实现,当然也可以通过链表来实现循环队列。
关于循环队列的状态和操作如下:
queue.front=0; queue.rear=0;
。queue.rear=(queue.rear+1)%MAXSIZE
。是为了当达到 MAXSIZE-1
位置后下一个位置自动到 0 去。queue.front=(queue.front+1)%MAXSIZE
。(queue.rear-queue.front+MAXSIZE)%MAXSIZE
。循环队列最大的问题就是如何判断队空和队满。之前顺序队列判断队空的条件 queue.front==queue.rear
是无法判断循环队列是队空还是队满的,因为循环队列队空和队满时 queue.front==queue.rear
条件都成立。如图:
为什么循环队列队满时,队头指针 front
和队尾指针 rear
会重合呢?如图所示,因为队头指针始终指向队头元素,而队尾指针指向队尾元素的下一个位置,所以当队满时,它们会重合。所以 queue.front==queue.rear
是无法作为单独判断队空和队满条件。
因此,为了区分是队空还是队满的清空,有三种处理方式:
(queue.rear+1)%MAXSIZE==queue.front
。queue.front==queue.rear
。(queue.rear-queue.front+MAXSIZE)%MAXSIZE
。结构体类型中增设表示元素个数的数据成员 size
。这两种情况都有 queue.front==queue.rear
,但不再作为判空或判满的条件。
queue.size==MAXSIZE
。queue.size==0
。结构体类型中增设 tag
数据成员,用来区分是队满还是队空。约定 tag 等于 0 时,若因删除导致 queue.front==queue.rear
则为队空;当 tag 等于 1 时,若因插入导致 queue.front==queue.rear
则为队满。有一道练习题就是实现该队列:Example004-设计一个循环队列,用 front 和 rear 分别作为队头和队尾指针,另外用一个标志 tag 表示队列是空还是不空。
queue.tag==1 && queue.front==queue.rear
。queue.tag==0 && queue.front==queue.rear
。/**
* 循环队列结构体定义
*/
typedef struct {
/**
* 数据域,存储循环队列中的数据
*/
int data[MAXSIZE];
/**
* 指针域,存储循环队列中队头元素的位置
*/
int front;
/**
* 指针域,存储循环队列中队尾元素的位置
*/
int rear;
} CircularQueue;
front
表示队头指针,实际上就是数组下标,指向顺序队列的队头元素;rear
表示队尾指针,指向顺序队列的队尾元素的下一个位置。rear
到达 MAXSIZE-1
位置,则会自动跳到 0 位置。注,完整代码请参考:
- CircularQueue.c
- CircularQueue.java
- CircularQueueTest.java
循环队列的常见操作如下:
void init(CircularQueue *queue)
:初始化循环队列。其中 queue
表示循环队列。int isEmpty(CircularQueue queue)
:判断循环队列是否为空。其中 queue
表示循环队列。如果循环队列为空则返回 1,否则返回 0。int isFull(CircularQueue queue)
:判断循环队列是否已满。其中 queue
表示循环队列。如果循环队列已满则返回 1,否则返回 0。int enQueue(CircularQueue *queue, int ele)
:将元素入队。其中 queue
表示循环队列;ele
表示待入队的元素。如果循环队列已满则不能入队,返回 0 表示入队失败;否则如果入队成功则返回 1。int deQueue(CircularQueue *queue, int *ele)
:将元素出队。其中 queue
表示循环队列;ele
用来保存出队的元素。如果循环队列为空则不能出队,返回 0 表示出队失败;否则如果出队成功则返回 1。int size(CircularQueue queue)
:获取循环队列中的元素个数。其中 queue
表示循环队列。返回循环队列中的元素个数。int getFront(CircularQueue queue, int *ele)
:读取循环队列中的队头元素。其中 queue
表示循环队列;ele
用来保存队头的元素。如果队列为空则无法获取队头元素则返回 0,否则返回 1。int getRear(CircularQueue queue, int *ele)
:读取循环队列中的队尾元素。其中 queue
表示循环队列;ele
用来保存队尾的元素。如果队列为空则无法获取队尾元素则返回 0,否则返回 1。void clear(CircularQueue *queue)
:清空循环队列中所有元素。其中 queue
表示循环队列。void print(CircularQueue queue)
:打印循环队列中所有元素。其中 queue
表示循环队列。init
初始化循环队列。
实现步骤:
front
和队尾指针 rear
都指向 0,表示空队列。实现代码如下:
/**
* 初始化循环队列
* @param queue 待初始化的循环队列
*/
void init(CircularQueue *queue) {
// 循环队列初始时,队头指针和队尾指针仍然都指向 0,表示是空队列
queue->front = 0;
queue->rear = 0;
}
isEmpty
判断循环队列是否为空。如果为空则返回 1,否则返回 0 表示非空。
实现步骤:
front
和队尾指针 rear
是否指向同一位置,即 queue.front==queue.rear
。实现代码如下:
/**
* 判断循环队列是否为空
* @param queue 循环队列
* @return 如果循环队列为空则返回 1,否则返回 0 表示非空
*/
int isEmpty(CircularQueue queue) {
// 只要队头指针和队尾指针相等,那么表示循环队列为空,无论指针在哪个位置
if (queue.rear == queue.front) {
return 1;
} else {
return 0;
}
}
isFull
判断循环队列是否已经满队。如果已满则返回 1,否则返回 0 表示未满。
实现步骤:
(queue.rear+1)%MAXSIZE==queue.front
,那么就认为队满。实现代码如下:
/**
* 判断循环队列是否已满
* @param queue 循环队列
* @return 如果循环队列已满则返回 1,否则返回 0 表示队列非满
*/
int isFull(CircularQueue queue) {
// 队尾指针再加上一,然后对 MAXSIZE 取余,如果等于队头指针,那么表示队满
if ((queue.rear + 1) % MAXSIZE == queue.front) {
return 1;
} else {
return 0;
}
}
enQueue
将元素入队。如果队未满才能入队,否则返回 0 表示入队失败。如果入队成功则返回 1。以 queue=[a, b, c]; ele=x
为例如图:
实现步骤:
ele
值。实现代码如下:
/**
* 将元素入队
* @param queue 循环队列
* @param ele 指定元素
* @return 如果队列已满则不能入队返回 0 表示入队失败;否则返回 1 表示入队成功
*/
int enQueue(CircularQueue *queue, int ele) {
// 0.参数校验,如果队满则不能入队
if ((queue->rear + 1) % MAXSIZE == queue->front) {
return 0;
}
// 1.将元素入队
// 1.1 先进行赋值,即将新元素填充到队尾指针指向的位置。因为队尾指针指向队尾元素的下一个位置
queue->data[queue->rear] = ele;
// 1.2 然后将队尾指针加一。因为是循环队列,如果到了队尾,那么又要从 0 开始,所以加一后需要对 MAXSIZE 进行取余
queue->rear = (queue->rear + 1) % MAXSIZE;
return 1;
}
deQueue
将元素出队。如果队空则不能出队,返回 0 表示出队失败。将出队元素保存到 ele,并返回 1 表示出队成功。以 queue=[a, b, c, d, e, f, g]
为例如图所示:
实现步骤:
ele
保存队头指针所指向的元素。实现代码如下:
/**
* 将元素出队
* @param queue 循环队列
* @param ele 用来保存出队的元素
* @return 如果队空则不能出队则将返回 0 表示出队失败;否则返回 1 表示出队成功
*/
int deQueue(CircularQueue *queue, int *ele) {
// 0.参数校验,如果队空则不能出队
if (queue->rear == queue->front) {
return 0;
}
// 1.将队头元素出队
// 1.1 用 ele 保存队头指针所指向的元素
*ele = queue->data[queue->front];
// 1.2 将队头指针加一,表示删除队头元素。因为是循环队列,所以要对 MAXSIZE 取余
queue->front = (queue->front + 1) % MAXSIZE;
// 1.3 返回 1 表示出队成功
return 1;
}
size
获取循环队列中实际的元素个数。
实现步骤:
(rear-front+MAXSIZE)%MAXISZE
。实现代码如下:
/**
* 获取循环队列中的元素个数
* @param queue 循环队列
* @return 队列中的元素个数
*/
int size(CircularQueue queue) {
// 如果是顺序队列,则元素个数是 rear-front
// 如果是循环队列,则元素个数是 (rear-front+MAXSIZE)%MAXSIZE
return (queue.rear - queue.front + MAXSIZE) % MAXSIZE;
}
getFront
读取队头元素,但并不出队。如果队空则不能读取,则返回 0,否则用 ele 保存队头元素,返回 1 表示读取成功。
实现步骤:
实现代码如下:
/**
* 获取循环队列的队头元素
* @param queue 循环队列
* @param ele 用来保存队头元素
* @return 如果队列为空则返回 0 表示获取失败;否则返回 1 表示获取成功。
*/
int getFront(CircularQueue queue, int *ele) {
// 0.参数校验,如果队列为空则没有队头元素,自然无法获取,所以返回 0 表示获取失败
if (queue.rear == queue.front) {
return 0;
}
// 1.用 ele 保存队头元素,即队头指针所指向的元素
*ele = queue.data[queue.front];
return 1;
}
getRear
读取循环队列的队尾元素。如果循环队空为空则返回 0 表示读取失败。否则用 ele
保存队尾元素,并返回 1 读取成功。
实现步骤:
实现代码如下:
/**
* 获取循环队列中的队尾元素
* @param queue 循环队列
* @param ele 用来保存队尾元素
* @return 如果队列为空则返回 0 表示获取失败;否则返回 1 表示获取成功。
*/
int getRear(CircularQueue queue, int *ele) {
// 0.参数校验,如果队列为空则没有队尾元素,自然无法获取,所以返回 0 表示获取失败
if (queue.rear == queue.front) {
return 0;
}
// 1.用 ele 保存队尾元素,由于队尾指针指向队尾元素的下一个位置,所以要队尾指针减一
*ele = queue.data[(queue.rear - 1 + MAXSIZE) % MAXSIZE];
return 1;
}
clear
清空循环队列。
实现步骤:
front
和队尾指针 rear
都指向 0,表示空队列。但实际上队列中原有的元素仍然存在,并没有被重置为某个值。实现代码如下:
/**
* 清空循环队列
* @param queue 循环队列
*/
void clear(CircularQueue *queue) {
// 即将队头指针和队尾指针都指向 0,表示恢复循环队列的初始状态,即空表
queue->front = 0;
queue->rear = 0;
}
print
打印循环队列中的所有有效元素。
实现步骤:
实现代码如下:
/**
* 打印循环队列中从队头到队尾的所有元素
* @param queue 循环队列
*/
void print(CircularQueue queue) {
printf("[");
int front = queue.front;
while (front != queue.rear) {
printf("%d", queue.data[front]);
if (front != (queue.rear - 1 + MAXSIZE) % MAXSIZE) {
printf(", ");
}
front = (front + 1) % MAXSIZE;
}
printf("]\n");
}
无。
s1
和 s2
来模拟一个队列,实现队列的出队、入队、队是否为空的运算