定义和特点
定义
循环队列(Circular Queue)是一种先进先出(FIFO)的数据结构,它允许在固定大小的数组中插入和删除元素。与普通队列不同的是,循环队列的尾部与头部相连,当尾部到达数组的最后一个位置时,它会回到数组的第一个位置。
循环队列通常由一个数组和两个指针(front 和 rear)组成。front 指针指向队列的第一个元素,而 rear 指针指向队列的最后一个元素的下一个位置。当队列为空时,两个指针都指向队列的开头。
特点
循环队列的特点包括:
- 循环队列的队头指针和队尾指针可以指示队列元素所在的位置,避免删除元素时移动大量元素。
- 循环队列只能队尾插入元素、在队头删除元素。
- 循环队列是先进先出(First In First Out)的线性表,先进入的元素出队,后进入的元素才能出队。
- 循环队列相比普通的队列,元素出队时无需移动大量元素,只需移动头指针。
- 循环队列适合处理用户排队等待的情况。
- 循环队列需要预先分配大量存储空间。
基本运算
- 入队(enqueue):在 rear 指针的下一个位置插入一个元素,并将 rear 指针向前移动一位。如果 rear 指针已经到达数组的末尾,则将其循环移动到数组的开头。
- 出队(dequeue):移除 front 指针所指向的元素,并将 front 指针向前移动一位。如果 front 指针已经到达数组的末尾,则将其循环移动到数组的开头。
- 判断队列是否为空:当 front 指针和 rear 指针都指向队列的开头时,队列为空。
- 判断队列是否已满:当 rear 指针指向队列的最后一个元素的下一个位置时,队列已满。
- 查看队首元素:返回 front 指针所指向的元素。
- 查看队列长度:返回 rear 指针与 front 指针之间的元素个数。
循环队列的实现
结构体
c
#define MAXSIZE 100
typedef int DataType;
typedef struct
{
DataType data[MAXSIZE];
int front;
int rear;
}CirclesQueue;
初始化
这个函数接收一个指向循环队列的指针CirclesQueue *Q
,然后设置队列的front
和rear
指针都指向队列的开头,也就是位置0。这样,队列就处于初始状态,没有任何元素。函数返回0,表示初始化成功
c
int init(CirclesQueue *Q)
{
Q->front = Q->rear = 0;
return 0;
}
入队
函数首先检查队列是否已满。如果队列已满,则打印一条消息"队列已满!100001",并返回一个错误代码100001。 如果队列没有满,那么函数会计算rear
指针的新位置。在循环队列中,当rear
指针到达数组的末尾时,它会循环回到数组的开头。这是通过将Q->rear
加1并对数组的最大大小MAXSIZE
取模来实现的。 最后,新元素x
被插入到Q->data[Q->rear]
的位置,然后函数返回0,表示操作成功。
c
int enqueue(CirclesQueue *Q, DataType x)
{
if(isfull(Q))
{
printf("队列已满!100001\n");
return 100001;
}
Q->rear = (Q->rear+1) % MAXSIZE;
Q->data[Q->rear] = x;
return 0;
}
出队
函数首先检查队列是否为空。如果队列为空,则打印一条消息"队列为空!100002",并返回一个错误代码100002。
如果队列不为空,那么函数会计算front
指针的新位置。在循环队列中,当front
指针到达数组的末尾时,它会循环回到数组的开头。这是通过将Q->front
加1并对数组的最大大小MAXSIZE
取模来实现的。
然后,函数会将被删除的元素赋值给*x
,这是通过从Q->data[Q->front]
获取元素并存储在*x
指向的位置来实现的。
c
int dequeue(CirclesQueue *Q, DataType *x)
{
if(isempty(Q))
{
printf("队列为空!100002\n");
return 100002;
}
Q->front = (Q->front+1) % MAXSIZE;
*x = Q->data[Q->front];
return 0;
}
判满和判空
这两个函数都是用于判断循环队列状态的函数。
isempty(CirclesQueue *Q)
函数检查队列是否为空。如果队列的front
指针和rear
指针相等,那么队列为空,函数返回1;否则,队列不为空,函数返回0。
isfull(CirclesQueue *Q)
函数检查队列是否已满。如果队列的rear
指针加1后对MAXSIZE
取模的结果等于front
指针,那么队列已满,函数返回1;否则,队列未满,函数返回0。
这两个函数都利用了循环队列的特性:在普通队列中,当front
指针到达数组的末尾时,它无法继续前进;而在循环队列中,当front
指针到达数组的末尾时,它可以循环回到数组的开头。同样的,对于rear
指针也是这样的。因此,当front
指针和rear
指针相等时,说明队列中没有元素;当rear
指针加1后对MAXSIZE
取模的结果等于front
指针时,说明队列已满。
c
int isempty(CirclesQueue *Q)
{
return (Q->front == Q->rear) ? 1 : 0;
}
int isfull(CirclesQueue *Q)
{
return (Q->rear+1)%MAXSIZE == Q->front ? 1 : 0;
}
输出队列内容
c
// 输出队列内容
void printQueue(CirclesQueue *Q) {
int i;
if (isempty(Q)) {
printf("Queue is empty.\n");
return;
}
i = (Q -> front) %MAXSIZE;
do{
printf(" %d",Q -> data[(i + 1 % MAXSIZE)]);
i = (i+1) %MAXSIZE;
}while(i != Q -> rear);
}
这段代码用于打印循环队列的内容。下面是这段代码的流程:
- 首先,函数检查队列是否为空。如果为空,则打印 "Queue is empty." 并返回。
- 如果队列不为空,则初始化一个变量
i
,该变量从队列的front
指针的位置开始。 - 然后,使用
do-while
循环遍历队列。在每次迭代中,打印当前i
指向的元素(通过计算(i + 1 % MAXSIZE)
),并将i
向前移动一位(i = (i+1) %MAXSIZE;
)。 - 循环继续进行,直到
i
到达队列的rear
指针的位置。
注意:这个函数假设队列中的元素是整数,并且使用 %MAXSIZE
来确保在数组的边界之外不会产生溢出。
获取队列长度
在循环队列中,队列的长度可以通过计算rear
指针和front
指针之间的距离来得到。如果rear
指针在front
指针的前面,那么队列的长度就是rear - front + MAXSIZE
;否则,队列的长度就是rear - front
。这里使用% MAXSIZE
是为了确保在计算队列长度时不会超出数组的边界。
c
// 获取队列长度
int getLength(CirclesQueue *Q) {
return (Q->rear - Q->front + MAXSIZE) % MAXSIZE; // 循环队列:若rear在前方,则长度为rear-front+MAXSIZE,否则为rear-front
}
获取队首元素
通过计算front
指针加1后对MAXSIZE
取模得到队首元素在数组中的位置,并返回该位置的元素值。这个函数适用于任何情况,无论front
指针在哪里,都可以正确获取队列的队首元素。
c
// 获取队首元素
DataType getFront(CirclesQueue* Q) {
int i;
i = (Q -> front) %MAXSIZE;
return Q -> data[(i + 1 % MAXSIZE)];
}
运行截图
完整代码
- CirclesQueue.h
c
/*
CirclesQueue.h
循环队列
*/
#define MAXSIZE 100
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);
// 输出队列内容
void printQueue(CirclesQueue *Q);
// 获取队列长度
int getLength(CirclesQueue *Q);
// 获取队首元素
DataType getFront(CirclesQueue* Q);
- CirclesQueue.c
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("队列已满!100001\n");
return 100001;
}
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("队列为空!100002\n");
return 100002;
}
Q->front = (Q->front+1) % MAXSIZE;
*x = Q->data[Q->front];
return 0;
}
/*队空*/
int isempty(CirclesQueue *Q)
{
return (Q->front == Q->rear) ? 1 : 0;
}
// 输出队列内容
void printQueue(CirclesQueue *Q) {
int i;
if (isempty(Q)) {
printf("Queue is empty.\n");
return;
}
i = (Q -> front) %MAXSIZE;
do{
printf(" %d",Q -> data[(i + 1 % MAXSIZE)]);
i = (i+1) %MAXSIZE;
}while(i != Q -> rear);
}
// 获取队列长度
int getLength(CirclesQueue *Q) {
return (Q->rear - Q->front + MAXSIZE) % MAXSIZE; // 循环队列:若rear在前方,则长度为rear-front+MAXSIZE,否则为rear-front
}
// 获取队首元素
DataType getFront(CirclesQueue* Q) {
int i;
i = (Q -> front) %MAXSIZE;
return Q -> data[(i + 1 % MAXSIZE)];
}
- mian.c
c
#include <stdio.h>
#include "CirclesQueue.h"
#include <string.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
CirclesQueue Q;
DataType x;
int cmd;
char yn;
int result;
char welcome[] = "(?ω?)没钱";
int i = 0;
int m = 0;
int n = 0;
for(i=0;i<strlen(welcome);i++)
{
printf("%c",welcome[i]);
for(m=0;m<10000;m++)
for(n=0;n<1000;n++)
{
;
}
}
printf("\n\n\n");
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~6):");
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)?");
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");
else printf("队列不为空!\n");
break;
case 5:
if(isfull(&Q)) printf("队列已满!\n");
else printf("队列还没有满!\n");
break;
case 6:
printf("队列的长度:%d\n",getLength(&Q));
break;
case 7:
printf("队列首元素: %d\n", getFront(&Q));
break;
case 8:
printf("输出队列:");
printQueue(&Q);
printf("\n");
break;
case 9:
printf("本程序为循环队列的演示程序,由许娜设计开发,\n本人作业如果问题,尽请指教学习!\n");
break;
}
}while(cmd!=0);
return 0;
}
总结
循环队列是一种先进先出(FIFO)的数据结构,它可以在固定大小的数组中实现队列的操作。相比于普通队列,循环队列的优点在于其队尾指针可以循环回到数组的开头,使得队列的操作更加高效。
循环队列的应用场景非常广泛,例如在操作系统中实现任务调度、在通信协议中实现数据包的存储和处理、在线程池中管理线程的执行等。循环队列的优点包括高效的插入和删除操作、空间利用率高、可以动态地调整队列大小等。
循环队列的特点包括:
- 队头和队尾指针可以循环移动,使得插入和删除操作更加高效。
- 队列的大小是固定的,但可以通过一些技巧扩展其容量。
- 队列中的元素是先进先出的,即最早插入的元素最早被删除。
- 循环队列需要一个额外的空间来区分空队列和满队列的情况。
在循环队列中,队头指针指向队首元素的下一位,队尾指针指向队尾元素的下一位。当插入元素时,队尾指针会向前移动一位;当删除元素时,队头指针会向前移动一位。当队头指针和队尾指针相遇时,说明队列为空;当队尾指针无法向前移动时,说明队列已满。
循环队列的优点在于其高效的插入和删除操作,但需要注意的是在实现时需要处理满队列和空队列的情况。此外,循环队列也需要一些技巧来扩展其容量,例如使用多级循环队列等。
总之,循环队列是一种非常实用的数据结构,它可以用于各种应用场景中,并且具有高效的性能和空间利用率高的优点。
参考文献
- 李刚 刘万辉. "线性表的结构分析和应用." 9787040461473: 16. 2017.1.1
- 文心一言