文章目录
- 前言
- 一、概念
- 二、代码实现
-
- [1. 循环队列结构](#1. 循环队列结构)
- [2. 创建循环链表(初始化)](#2. 创建循环链表(初始化))
-
- [3. 判断队列是否满了](#3. 判断队列是否满了)
- [4. 判断队列是否为空](#4. 判断队列是否为空)
- [5. 往队列中插入数据](#5. 往队列中插入数据)
- [6. 从队列中删除数据](#6. 从队列中删除数据)
- [7. 返回队头元素](#7. 返回队头元素)
- [8. 返回队尾元素](#8. 返回队尾元素)
- [9. 销毁队列](#9. 销毁队列)
- 总结
前言
在上一篇中,我们实现了基于链表的普通队列。本篇作为队列章节的补充,介绍一种更高效的变体------循环队列。它通过首尾相连的环形结构复用出队后的空间,并采用"多申请一个位置"的技巧区分队空与队满。本文将从结构定义入手,逐步实现创建、入队、出队、取队头/队尾、判空、判满及销毁等接口,帮助你进一步完善队列知识体系。
一、概念
实际中还有一种特殊的队列叫循环队列,环形队列首尾相连成环,环形队列可以使用数组实现,也可以使用循环链表实现

本文用数组来实现循环队列
二、代码实现
1. 循环队列结构
c
typedef struct {
//存储整型类型的数据
int* arr; //存储整型数据的动态数组指针
int front; //对头下标
int rear; //队尾下标
int capacity; //循环队列空间大小
} MyCircularQueue;
-
arr作是存储整型数据的动态数组指针,作为循环队列的元素存储数组
-
front和rear分别为队头下标 和队尾下标,用于实时标记队头队尾的位置
-
capacity为循环队列的空间大小,不过我们每次申请空间要申请 (capacity+1)个,方便后续操作
2. 创建循环链表(初始化)
c
MyCircularQueue* myCircularQueueCreate(int k) {
//创建循环队列,并向操作系统申请空间
MyCircularQueue* pq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
if (pq == NULL)
{
perror("malloc fail");
return NULL;
}
//给存储整型数组的指针分配空间,申请k+1个空间
pq->arr = (int*)malloc(sizeof(int) * (k + 1));
if (pq->arr == NULL)
{
perror("malloc fail");
free(pq);
return NULL;
}
//初始情况下头下标和尾下标都为0
pq->front = pq->rear = 0;
//capacity为k
pq->capacity = k;
return pq;
}
-
- 先向操作系统申请循环队列这个结构体
pq的内存空间,MyCircularQueue* pq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
- 先向操作系统申请循环队列这个结构体
-
- 再给循环队列的储存数组
arr申请(k+1)个int大小的空间,
- 再给循环队列的储存数组
-
- 初始情况下头下标和尾下标都为0,
capacity为k
- 初始情况下头下标和尾下标都为0,
申请k+1个空间的原因:
申请 k + 1 k+1 k+1 个空间是为了空出一个闲置位置,以此区分"队空"和"队满"的状态 。如果不额外多开辟一个位置,当队列全满(存了 k k k 个元素)和队列全空(存了 0 0 0 个元素)时,队头与队尾指针都会重合指向同一位置(即
front == rear),程序将无法仅凭下标判断此时究竟是满还是空;而多开辟一个空间后,约定保留一个空位不存数据,就能清晰地区分:front == rear时为"队空",而(rear + 1) % (k + 1) == front时为"队满"。
3. 判断队列是否满了
c
bool myCircularQueueIsFull(MyCircularQueue* obj) {
//如果rear往后再走一步就是front,就说明队列满了
return (obj->rear + 1) % (obj->capacity + 1) == (obj->front);
}
- 如果rear往后再走一步就是front,就说明队列满了
4. 判断队列是否为空
c
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
//队尾等于队头
return obj->rear == obj->front;
}
- 当队尾等于对头,说明队列为空
5. 往队列中插入数据
c
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
//往循环队列插入数据
//如果队列满了,则不能插入数据
if (myCircularQueueIsFull(obj))
{
return false;
}
obj->arr[obj->rear] = value;
obj->rear++;
//防止rear越界
obj->rear %= (obj->capacity + 1);
return true;
}
-
- 先判断队列是否满了,若满了,则不能插入
-
- 若未满,在
rear位置插入数据,并让rear++,往后移动一位
- 若未满,在
-
- 此时要防止
rear越界,用obj->rear %= (obj->capacity + 1)操作保障rear不越界
- 此时要防止
-
- 成功插入后,返回
true
- 成功插入后,返回
6. 从队列中删除数据
c
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
//判断循环队列是否为空
if (myCircularQueueIsEmpty(obj))
{
return false;
}
//不为空
obj->front++;
//防止front越界
obj->front %= (obj->capacity + 1);
return true;
}
-
- 先判断队列是否为空
-
- 不为空则让
front++,表示删除原front指向的元素
- 不为空则让
-
obj->front %= (obj->capacity + 1)防止front越界
7. 返回队头元素
c
int myCircularQueueFront(MyCircularQueue* obj) {
//判断队列是否为空
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->arr[obj->front];
}
-
- 向判断是否为空队列
-
- 不为空队列则返回下标为
front的元素
- 不为空队列则返回下标为
8. 返回队尾元素
c
int myCircularQueueRear(MyCircularQueue* obj) {
//判断队列是否为空
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
int prev = obj->rear - 1;
//如果rear是指向的是下标为0的位置
if (obj->rear == 0)
{
prev = obj->capacity;
}
return obj->arr[prev];
}
-
- 先检查不为空队列
-
- 因为
rear始终指向的是队尾后一个位置,所以真正队尾元素下标为rear-1,创建prev来接收真正队尾元素下标
- 因为
-
- 返回下标为
prev的元素
- 返回下标为
9. 销毁队列
c
void myCircularQueueFree(MyCircularQueue* obj) {
//如果obj->arr不为空,则free(arr)
if (obj->arr)
free(obj->arr);
free(obj);
obj = NULL;
}
-
- 如果
obj->arr不为空,则free(arr)
- 如果
-
- 最后释放
obj,并将obj置为NULL
- 最后释放
总结
以上就是本篇补充的全部内容。本文在链式队列的基础上,介绍了循环队列的数组实现,重点讲解了如何通过申请 k+1 个空间并利用取模运算来区分队空与队满。详细实现了创建、判满、判空、入队、出队、取队头、取队尾及销毁等接口。掌握循环队列,能让你对队列的空间管理与环形缓冲思想有更深入的理解。