数据结构——队列-循环队列
一、循环队列的定义
循环队列是顺序队列结构,将顺序存储队列的元素的一维数组首尾相连
可以这么理解:顺序队列的首尾相连=循环队列
数据结构——队列-顺序队列
1、循环队列的特点
先进先出(First In First Out,FIFO)(前出后进)
二、循环队列的相关术语
1、入队(EmQueue)
在队列中插入元素
2、出队(DeQueue)
在队列中删除元素
3、队头
允许进行删除的一端
队头元素
处于队头位置的数据元素
4、队尾
允许进行插入操作的一端
队尾元素
处于队尾位置的数据元素
5、空队
不含任何数据元素的队列
6、队满
整个队列的数组已被存储相应的元素
三、循环队列的基本操作及代码实现
相关头文件
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
typedef int DataType;
定义循环队列的结构体
循环队列的结构体需要有:
- 两个指针——头指针(front指向队头)、尾指针(rear指向队尾)
- 一个数组——存储已入队的数据
与顺序列表的结构体代码一样
//结构体
typedef struct
{
DataType data[MAXSIZE];
int front;
int rear;
} Queue;
初始化循环队列(InitQueue)
初始条件:循环队列不存在
做法:设置头指针front、尾指针rear指向数组下标为0的位置;
操作结果:构造一个空的循环队列Q
//初始化循环队列
void InitQueue(Queue *Q)
{
Q->front = 0;
Q->rear = 0;
}
判断循环队列空(EmptyQueue)
初始条件:队列Q存在
核心思想:当头指针front、尾指针rear指同时向数组下标为0的位置为空
操作结果:判断是否是空队列,若队列Q为空,则返回1;否则返回0
//判断循环队列空
int EmptyQueue(Queue *Q)
{
if (Q->front == Q->rear)
return 1;
return 0;
}
判断循环队列满(FullQueue)
初始条件:循环队列Q存在
核心思想:
当头指针front、尾指针rear满足:“(rear+1)%MAXSIZE=front ”时,循环队列为满(MAXSIZE是数组可存储的最大值)
操作结果:判断是否已经满,若循环队列Q为满,则返回1;否则返回0
//判断循环队列满
int FullQueue(Queue *Q)
{
if (Q->front == (Q->rear + 1) % MAXSIZE)
return 1;
return 0;
}
入队(EnQueue)
初始条件:循环队列Q已存在且非满
核心思想:
- 队尾指针(rear)指向下一个位置
- 把要入队的值赋值给尾指针rear指向的数组位置
操作结果:数据入队
//入队
void EnQueue(Queue *Q, DataType x)
{
if (FullQueue(Q))
{
printf("循环队列已满,无法进行入队操作!\n");
return;
}
Q->rear++;
Q->data[Q->rear] = x;
}
出队(DeQueue)
初始条件:循环队列Q已存在且非空
核心思想:
- 更新头指针(头指针front向后指)
- 打印输出要出队的元素(头指针front指向的数据)
操作结果:打印队头元素,并从队列Q中删除当前队头元素,而其后继元素称为队头元素
//出队
void DeQueue(Queue *Q)
{
if (EmptyQueue(Q))
{
printf("循环队列为空,无法进行出队操作!\n");
return;
}
Q->front++;
printf("当前出队元素为:%d\n", Q->data[Q->front]);
}
取队头元素(GetFront)
初始条件:循环队列Q存在且不为空
思想:队头元素就是头指针front所指的下一个位置的元素
操作结果:将队头元素打印,循环队列Q不发生变化
//取队头元素
int GetFront(Queue *Q)
{
if (EmptyQueue(Q))
return -1;
return Q->data[Q->front + 1];
}
取队尾元素(GetRear)
初始条件:循环队列Q存在且不为空
思想:队尾元素就是尾指针rear所指的数据
操作结果:将队尾元素打印,循环队列Q不发生变化
//取队尾元素
int GetRear(Queue *Q)
{
if (EmptyQueue(Q))
return -1;
return Q->data[Q->rear];
}
获取循环队列元素个数(GetQueueCouunt)
初始条件:循环队列Q存在
操作结果:打印循环队列个数,循环队列Q不发生变化
//获取循环队列元素个数
int GetQueueCount(Queue *Q)
{
return Q->rear - Q->front;
}
显示循环队列元素(ShowQueue)
初始条件:循环队列Q存在且不为空
操作:将循环队列Q中所有元素从队头开始打印
//显示循环队列元素
void ShowQueue(Queue *Q)
{
if (EmptyQueue(Q))
return;
int n = GetQueueCount(Q);
int j = Q->front;
for (int i = 1; i <= n; i++)
{
j++;
printf("%d ", Q->data[j]);
}
printf("\n");
}
清空循环队列(ClearQueue)
初始条件:循环队列Q存在且不为空
核心思想:头指针front和尾指针rear指向循环队列下标为0的位置
操作:将循环队列Q中所有元素清空
//清空循环队列
void ClearQueue(Queue *Q)
{
Q->rear = Q->front = 0;
printf("循环队列已清空!\n");
}
main函数
测试以上代码是否正确
// main函数
int main()
{
int n, i, number = 0;
Queue *Q = (Queue *)malloc(sizeof(Queue));
; // 在栈上分配内存
// 初始化队列
InitQueue(Q);
//入队
printf("要入队的元素个数是:");
scanf("%d", &n);
printf("请输入你要入队的元素:");
for (i = 0; i < n; i++)
{
scanf("%d", &number);
EnQueue(Q, number);
}
ShowQueue(Q);
//出队
DeQueue(Q);
ShowQueue(Q);
//取队头元素
printf("此时队头元素为:%d\n", GetFront(Q));
ShowQueue(Q);
//取队尾元素
printf("此时队尾元素为:%d\n", GetRear(Q));
ShowQueue(Q);
//获取元素个数
printf("此时元素个数为:%d\n", GetQueueCount(Q));
ShowQueue(Q);
//清空栈
ClearQueue(Q);
ShowQueue(Q);
return 0;
}
四、循环队列的优缺点
缺点
- 判断队空队满复杂:不能简单地通过比较队头和队尾指针是否相等来判断队空或队满。
- 数据容量固定:它的最大容量是预先确定的,这在某些动态变化的应用场景中可能会受到限制。可通过扩容操作解决,但扩容操作对于循环队列来说相对复杂,并且可能涉及到数据的重新分配和移动。
优点
- 高效利用空间:当队头元素出队后,队头前面的空间可以被重新利用;
(通过将数组视为一个环形结构,使得队头前面的空间在队列循环使用过程中能够被再次利用) - 便于实现和管理:主要通过调整队头和队尾指针来实现入队和出队操作。操作逻辑与普通队列相似,容易理解和编码实现。
五、完整代码
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
typedef int DataType;
//结构体
typedef struct
{
DataType data[MAXSIZE];
int front;
int rear;
} Queue;
//初始化循环队列
void InitQueue(Queue *Q)
{
Q->front = 0;
Q->rear = 0;
}
//判断循环队列空
int EmptyQueue(Queue *Q)
{
if (Q->front == Q->rear)
return 1;
return 0;
}
//判断循环队列满
int FullQueue(Queue *Q)
{
if (Q->front == (Q->rear + 1) % MAXSIZE)
return 1;
return 0;
}
//入队
void EnQueue(Queue *Q, DataType x)
{
if (FullQueue(Q))
{
printf("循环队列已满,无法进行入队操作!\n");
return;
}
Q->rear++;
Q->data[Q->rear] = x;
}
//出队
void DeQueue(Queue *Q)
{
if (EmptyQueue(Q))
{
printf("循环队列为空,无法进行出队操作!\n");
return;
}
Q->front++;
printf("当前出队元素为:%d\n", Q->data[Q->front]);
}
//取队头元素
int GetFront(Queue *Q)
{
if (EmptyQueue(Q))
return -1;
return Q->data[Q->front + 1];
}
//取队尾元素
int GetRear(Queue *Q)
{
if (EmptyQueue(Q))
return -1;
return Q->data[Q->rear];
}
//获取循环队列元素个数
int GetQueueCount(Queue *Q)
{
return Q->rear - Q->front;
}
//显示循环队列元素
void ShowQueue(Queue *Q)
{
if (EmptyQueue(Q))
return;
int n = GetQueueCount(Q);
int j = Q->front;
for (int i = 1; i <= n; i++)
{
j++;
printf("%d ", Q->data[j]);
}
printf("\n");
}
//清空循环队列
void ClearQueue(Queue *Q)
{
Q->rear = Q->front = 0;
printf("循环队列已清空!\n");
}
// main函数
int main()
{
int n, i, number = 0;
Queue *Q = (Queue *)malloc(sizeof(Queue));
; // 在栈上分配内存
// 初始化队列
InitQueue(Q);
//入队
printf("要入队的元素个数是:");
scanf("%d", &n);
printf("请输入你要入队的元素:");
for (i = 0; i < n; i++)
{
scanf("%d", &number);
EnQueue(Q, number);
}
ShowQueue(Q);
//出队
DeQueue(Q);
ShowQueue(Q);
//取队头元素
printf("此时队头元素为:%d\n", GetFront(Q));
ShowQueue(Q);
//取队尾元素
printf("此时队尾元素为:%d\n", GetRear(Q));
ShowQueue(Q);
//获取元素个数
printf("此时元素个数为:%d\n", GetQueueCount(Q));
ShowQueue(Q);
//清空栈
ClearQueue(Q);
ShowQueue(Q);
return 0;
}
本章为数据结构——队列-循环队列,后续将继续更新链队列