详解循环队列

276 阅读8分钟

循环队列的定义及其特点

定义

循环队列是一种使用数组实现,能够高效地处理队列元素的入队和出队操作的数据结构。它通过将队列的头部和尾部相连,形成一个逻辑上的环状空间,从而避免了队列空间的浪费。循环队列通过维护头指针和尾指针来跟踪队列状态,入队和出队操作都在固定位置进行。循环队列提高了队列的利用率,且具有较好的时间复杂度。

特点

  1. 环形存储结构:循环队列通过将队列的尾部与头部提高形成连通,一个结构来实现元素的循环利用。这样可以充分利用阵列空间,存储效率。
  2. 具有队头指针和队尾指针,指示队列元素所在的位置,避免删除元素时移动大量元素。
  3. 只能队尾插入元素、在队头删除元素。
  4. 先进先出(First In First Out)的线性表,先进入的元素出队,后进入的元素才能出队。
  5. 相比普通的队列,元素出队时无需移动大量元素,只需移动头指针。
  6. 适合处理用户排队等待的情况。
  7. 需要预先分配大量存储空间。
  8. 循环利用队列空间:当队列尾部的指针后部引用队列的最后一个位置时,如果队列头部的指针前部没有指向队列的起始位置,则可以将后面的空间利用起来,实现元素的循环使用。

循环队列的运算

初始化

指向循环队列结构体的指针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);
	
}

运行结果

QQ截图20231025111308.png

QQ截图20231025112121.png

QQ截图20231025204746.png

小结

当我们需要实现一个队列时,循环队列是一种常用的数据结构。循环队列可以有效地利用队列底层的存储空间,实现高效的入队和出队操作。

循环队列的特点如下:

  • 使用数组作为底层存储结构,通过循环利用数组元素实现队列的循环性。
  • 需要两个指针来标识队头和队尾,分别称为frontrear
  • 初始时,frontrear指针都指向数组的第一个位置。
  • 入队操作时,先将rear指针向后移动一位,然后将元素插入rear指针所在位置。
  • 出队操作时,先将front指针向后移动一位,然后返回front指针所在位置的元素。
  • rear指针追赶上front指针时,表示队列已满。
  • front指针等于rear指针时,表示队列为空。

循环队列的优点:

  • 入队和出队操作的时间复杂度都是O(1),即常数时间。
  • 可以充分利用数组的空间,避免了元素迁移的开销。
  • 实现简单,代码量较少。

循环队列的缺点:

  • 需要事先确定队列的最大容量,不支持动态扩容。

总结:循环队列是一种高效的队列实现方式,适用于需要频繁进行入队和出队操作的场景。它通过循环利用数组元素实现队列的循环性,具有入队和出队操作的时间复杂度都是O(1)的优点。

参考文献

ora.ai

文心一言