队列文档之循环队列

循环队列

定义

概念

为了解决顺序队列“假溢出”的缺陷,所以引入了循环队列。

关于顺序队列请参考:顺序队列

循环队列就是将顺序队列臆造成一个环状的空间(实际上不是,只是把它看成是环状的),即把存储队列元素的顺序表从逻辑上视为一个环。当队头指针 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");
}

注意事项

无。

练习题

  • 15
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值