队列文档之循环队列

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");

注意事项

无。

练习题

以上是关于队列文档之循环队列的主要内容,如果未能解决你的问题,请参考以下文章

循环队列

顺序队列总结

java-----循环队列

java-----循环队列

大话数据结构----循环队列和链式队列

什么是环形队列,采用什么方法实现环形队列