队列文档之循环队列
Posted 二木成林
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了队列文档之循环队列相关的知识,希望对你有一定的参考价值。
循环队列
定义
概念
为了解决顺序队列“假溢出”的缺陷,所以引入了循环队列。
关于顺序队列请参考:顺序队列。
循环队列就是将顺序队列臆造成一个环状的空间(实际上不是,只是把它看成是环状的),即把存储队列元素的顺序表从逻辑上视为一个环。当队头指针 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 位置。 - 循环队列能充分利用队列中的空余空间,直到队列中所有空间(除了用来判断队空或队满的一个空间)都被使用。
基本操作
注,完整代码请参考:
概述
循环队列的常见操作如下:
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
为例如图:
实现步骤:
- 参数校验,如果队满则不能入队。返回 0 表示入队失败。
- 先进行赋值,将队尾指针所指向的位置赋予
ele
值。 - 接着队尾指针加一,指向队尾元素的下一个位置。保证队尾指针始终指向队尾元素的下一位置。
- 返回 1 表示入队成功。
实现代码如下:
/**
* 将元素入队
* @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]
为例如图所示:
实现步骤:
- 参数校验,如果队空,则不能出队。返回 0 表示出队失败。
- 将元素出队。用
ele
保存队头指针所指向的元素。 - 然后将队头指针加一,保证队头指针始终指向队头元素。
- 返回 1 表示出队成功。
实现代码如下:
/**
* 将元素出队
* @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 表示读取成功。
实现步骤:
- 参数校验,如果队空则没有队头元素,自然也无法获取。返回 0 表示读取失败。
- 直接读取队头指针所指向的元素。因为队头指针始终指向队头元素。
实现代码如下:
/**
* 获取循环队列的队头元素
* @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 读取成功。
实现步骤:
- 参数校验,如果队空,则不能读取队尾元素。返回 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");
注意事项
无。
练习题
- Example001-用两个栈
s1
和s2
来模拟一个队列,实现队列的出队、入队、队是否为空的运算 - Example004-设计一个循环队列,用 front 和 rear 分别作为队头和队尾指针,另外用一个标志 tag 表示队列是空还是不空
- Example005-Q 是一个队列,S 是一个空栈,实现将队列中的元素逆置的算法
以上是关于队列文档之循环队列的主要内容,如果未能解决你的问题,请参考以下文章