循环队列的定义及其特点
定义
循环队列是一种使用数组实现,能够高效地处理队列元素的入队和出队操作的数据结构。它通过将队列的头部和尾部相连,形成一个逻辑上的环状空间,从而避免了队列空间的浪费。循环队列通过维护头指针和尾指针来跟踪队列状态,入队和出队操作都在固定位置进行。循环队列提高了队列的利用率,且具有较好的时间复杂度。
特点
- 环形存储结构:循环队列通过将队列的尾部与头部提高形成连通,一个结构来实现元素的循环利用。这样可以充分利用阵列空间,存储效率。
- 具有队头指针和队尾指针,指示队列元素所在的位置,避免删除元素时移动大量元素。
- 只能队尾插入元素、在队头删除元素。
- 先进先出(First In First Out)的线性表,先进入的元素出队,后进入的元素才能出队。
- 相比普通的队列,元素出队时无需移动大量元素,只需移动头指针。
- 适合处理用户排队等待的情况。
- 需要预先分配大量存储空间。
- 循环利用队列空间:当队列尾部的指针后部引用队列的最后一个位置时,如果队列头部的指针前部没有指向队列的起始位置,则可以将后面的空间利用起来,实现元素的循环使用。
循环队列的运算
初始化
指向循环队列结构体的指针Q作为参数,并将队头指针front和队尾指针rear都设置为0。
/*循环队列初始化*/
int init(CirclesQueue *Q)
{
Q->front = Q->rear = 0;
return 0;
}
入队
首先判断队列是否已满,如果已满则打印提示信息并返回错误码。如果队列未满,则将队尾指针rear按照循环队列的规则进行更新,即将其加1并取模MAXSIZE,确保在数组范围内。然后将元素x存储到新的队尾位置。
/*入队*/
int enqueue(CirclesQueue *Q, DataType x)
{
if(isfull(Q))
{
printf("队列已满!10003\n");
return 10003;
}
Q->rear = (Q->rear+1) % MAXSIZE;
Q->data[Q->rear] = x;
return 0;
}
队满?
通过判断队尾指针rear加1取模MAXSIZE后是否等于队头指针front来判断队列是否已满。
/*队满?*/
int isfull(CirclesQueue *Q)
{
return (Q->rear+1)%MAXSIZE == Q->front ? 1 : 0;
}
出队
首先判断队列是否为空,如果为空则打印提示信息并返回错误码。如果队列非空,则将队头指针front按照循环队列的规则进行更新,即将其加1并取模MAXSIZE,确保在数组范围内。然后将队头元素赋值给指针x所指向的变量。
/*出队*/
int dequeue(CirclesQueue *Q, DataType *x)
{
if(isempty(Q))
{
printf("队列为空!10003\n");
return 10003;
}
Q->front = (Q->front+1) % MAXSIZE;
*x = Q->data[Q->front];
return 0;
}
队空
通过判断队头指针front和队尾指针rear是否相等来判断队列是否为空。
/*队空*/
int isempty(CirclesQueue *Q)
{
return (Q->front == Q->rear) ? 1 : 0;
}
输出个数
声明一个变量 size 并初始化为 0。首先判断队尾指针rear是否大于队头指针front。如果是,则说明队列中元素是连续存放的,直接计算rear - front即可得到元素个数。如果是指针rear小于队头指针front,需要计算MAXSIZE - (front - rear)才能得到正确的元素个数。队尾指针rear等于队头指针front,则为0。
/*输出个数*/
int getsize(CirclesQueue *Q)
{
int size = 0; ;
if(Q->rear > Q->front){
size = Q->rear - Q->front;
} else if(Q->rear < Q->front){
size = MAXSIZE - (Q->front - Q->rear);
}
return size;
}
获取队首
首先判断队列是否为空,如果为空则打印提示信息并返回错误码。如果队列非空,则根据循环队列的规则计算出队首元素的下标i,即(front + 1) % MAXSIZE。然后将队首元素赋值给指针x所指向的变量。
/*获取队首*/
int getfront(CirclesQueue *Q, DataType *x)
{
if(isempty(Q)){
printf("队列为空!10003\n");
return 10003;
}
int i;
i = (Q->front+1) % MAXSIZE;
*x = Q->data[i];
return 0;
}
输出队列
首先判断队列是否为空,如果为空则打印提示信息并返回。如果队列非空,则从 Q->front(队列头部的索引)开始,并递增索引,打印出队列中的每个元素。当索引达到 MAXSIZE 时,它回滚到0,即(Q->front) % MAXSIZE。这个过程一直持续到索引等于 Q->rear(队列尾部的索引)为止。
/*输出队列*/
void print(CirclesQueue *Q)
{
int i;
if(isempty(Q))
{
printf("队列为空!\n");
return;
}
printf("队内所有元素为:");
i = (Q->front) % MAXSIZE;
do{
printf(" %d", Q->data[(i + 1) % MAXSIZE]);
i = (i + 1) % MAXSIZE;
} while (i != Q->rear);
}
循环队列的实现
完整代码
项目结构
CirclesQueue.c
CirclesQueue.h
main.c
项目文件
main.c
#include <stdio.h>
#include "CirclesQueue.h"
int main(int argc, char* argv[])
{
CirclesQueue Q;
DataType x;
int cmd;
char yn;
do
{
printf("-----------循环队列演示-----------\n");
printf(" 1. 初始化\n");
printf(" 2. 入队\n");
printf(" 3. 出队\n");
printf(" 4. 队空?\n");
printf(" 5. 队满\n");
printf(" 6. 队内元素个数\n");
printf(" 7. 队首元素\n");
printf(" 8. 输出循环队列所有元素\n");
printf(" 9. 帮助\n");
printf(" 0. 退出\n");
printf(" 请选择(0~9):");
scanf("%d",&cmd);
switch(cmd)
{
case 1:
init(&Q);
printf("队列已初始化!\n");
break;
case 2:
printf("请输入要入队的元素x=");
scanf("%d", &x);
if(!enqueue(&Q,x))
{
printf("元素x=%d已入队\n", x);
}
break;
case 3:
printf("确定要出队(出队会将删除对首元素, y or n, n)?");
// flushall();
getchar();
scanf("%c", &yn);
if(yn == 'y' || yn == 'Y')
{
if(!dequeue(&Q,&x))
{
printf("队首元素【%d】已出队!\n", x);
}
}
break;
case 4:
if(isempty(&Q)){
printf("队列为空,还可以添加元素哦!\n");
break;
}
printf("队列有元素\n");
break;
case 5:
if(isfull(&Q)){
printf("满了哦!可以选择出队哦!\n");
break;
}
printf("队列还未满,还可以添加元素哦!\n");
break;
case 6:
printf("队列长度为:%d \n",getsize(&Q));
break;
case 7:
if(!getfront(&Q, &x)){
printf("队首元素为:%d\n", x);
}
break;
case 8:
print(&Q);
break;
case 9:
printf("本程序为顺序栈的演示程序,由曾苗强设计开发。\n对该文章有疑问欢迎交流!\n");
}
}while(cmd!=0);
return 0;
}
CirclesQueue.h
/*
CirclesQueue.h
循环队列
*/
#define MAXSIZE 5
typedef int DataType;
typedef struct
{
DataType data[MAXSIZE];
int front;
int rear;
}CirclesQueue;
/*循环队列初始化*/
int init(CirclesQueue *Q);
/*入队*/
int enqueue(CirclesQueue *Q, DataType x);
/*队满?*/
int isfull(CirclesQueue *Q);
/*出队*/
int dequeue(CirclesQueue *Q, DataType *);
/*队空*/
int isempty(CirclesQueue *Q);
/*输出个数*/
int getsize(CirclesQueue *Q);
/*获取队首*/
int getfront(CirclesQueue *Q, DataType *x);
/*输出队列*/
void print(CirclesQueue *Q);
CirclesQueue.c
/*
CirclesQueue.c
*/
#include "CirclesQueue.h"
/*循环队列初始化*/
int init(CirclesQueue *Q)
{
Q->front = Q->rear = 0;
return 0;
}
/*入队*/
int enqueue(CirclesQueue *Q, DataType x)
{
if(isfull(Q))
{
printf("队列已满!10003\n");
return 10003;
}
Q->rear = (Q->rear+1) % MAXSIZE;
Q->data[Q->rear] = x;
return 0;
}
/*队满?*/
int isfull(CirclesQueue *Q)
{
return (Q->rear+1)%MAXSIZE == Q->front ? 1 : 0;
}
/*出队*/
int dequeue(CirclesQueue *Q, DataType *x)
{
if(isempty(Q))
{
printf("队列为空!10003\n");
return 10003;
}
Q->front = (Q->front+1) % MAXSIZE;
*x = Q->data[Q->front];
return 0;
}
/*队空*/
int isempty(CirclesQueue *Q)
{
return (Q->front == Q->rear) ? 1 : 0;
}
/*输出个数*/
int getsize(CirclesQueue *Q)
{
int size = 0;
if(Q->rear > Q->front){
size = Q->rear - Q->front;
} else if(Q->rear < Q->front){
size = MAXSIZE - (Q->front - Q->rear);
}
return size;
}
/*获取队首*/
int getfront(CirclesQueue *Q, DataType *x)
{
if(isempty(Q)){
printf("队列为空!10003\n");
return 10003;
}
int i;
i = (Q->front+1) % MAXSIZE;
*x = Q->data[i];
return 0;
}
/*输出队列*/
void print(CirclesQueue *Q)
{
int i;
if(isempty(Q))
{
printf("队列为空!\n");
return;
}
printf("队内所有元素为:");
i = (Q->front) % MAXSIZE;
do{
printf(" %d", Q->data[(i + 1) % MAXSIZE]);
i = (i + 1) % MAXSIZE;
} while (i != Q->rear);
}
运行结果
小结
当我们需要实现一个队列时,循环队列是一种常用的数据结构。循环队列可以有效地利用队列底层的存储空间,实现高效的入队和出队操作。
循环队列的特点如下:
- 使用数组作为底层存储结构,通过循环利用数组元素实现队列的循环性。
- 需要两个指针来标识队头和队尾,分别称为
front和rear。 - 初始时,
front和rear指针都指向数组的第一个位置。 - 入队操作时,先将
rear指针向后移动一位,然后将元素插入rear指针所在位置。 - 出队操作时,先将
front指针向后移动一位,然后返回front指针所在位置的元素。 - 当
rear指针追赶上front指针时,表示队列已满。 - 当
front指针等于rear指针时,表示队列为空。
循环队列的优点:
- 入队和出队操作的时间复杂度都是O(1),即常数时间。
- 可以充分利用数组的空间,避免了元素迁移的开销。
- 实现简单,代码量较少。
循环队列的缺点:
- 需要事先确定队列的最大容量,不支持动态扩容。
总结:循环队列是一种高效的队列实现方式,适用于需要频繁进行入队和出队操作的场景。它通过循环利用数组元素实现队列的循环性,具有入队和出队操作的时间复杂度都是O(1)的优点。