一、循环队列的相关知识

前面讲到在通过顺序存储结构来判断顺序队列是否为满队时,提及到会存在“假溢出”现象,这里就可以通过循环队列来解决。

所谓循环队列,也就是将顺序队列中的一维数组首尾相连成环,在逻辑上视为一个环连接起来,其存储类型定义与顺序队列的存储类型定义是一样的,也是定义数组data[MaxSize]和两个指针front、rear,即队头指针front指向队头元素,队尾指针rear指向队尾元素,如下代码:

#define MaxSize 20		//定义队列长度
//循环队列的定义
typedef struct {
	int data[MaxSize];
	int front,rear;	//队头指针和队尾指针
} SqQueue;

例如,下图是个空循环队列,其中data[]数组中未存任何数据元素,指针front、rear都指向同一位置,此时队列为空:
在这里插入图片描述

二、循环队列的代码实现

(一)循环队列的初始化

循环队列的初始化与顺序队列的初始化代码一样,当Q.front=Q.rear=0时,表示循环队列的初始状态,此时队列为空【队头指针和队尾指针都等于0】,如下代码:

//循环队列的初始化
void InitQueue(SqQueue &Q) {
	Q.front=Q.rear=0;	//队头指针和队尾指针都指向0
}

(二)循环队列判空

循环队列的判空代码与顺序队列也是一样,当判断顺序队列是否为空队时,可以判断队头指针front和队尾指针rear是否相等,即if条件语句为Q.front==Q.rear,如下代码:

//判断循环队列是否为空队
bool EmptyQueue(SqQueue Q){
	if(Q.front==Q.rear)	//当队头指针front等于队尾指针rear时队列为空 
		return true;
	else
		return false;
}

(三)循环队列判满

判断循环队列是否为满队就要另外写代码,由于我们采用循环队列实现队列,我们规定:牺牲一个存储单元来区分是否满队,入队时少使用一个队列单元,即队头指针在队尾指针的下一个位置时,此时作为满队的条件,如下图:
在这里插入图片描述
这里我们可以知道,我们定义的MaxSize=7,即data[MaxSize],采用“%”取余符号,来定义每次入队、出队和判断队列是否为满队的代码逻辑,假定x为数字,MaxSize已知为7,即x%7的所有可能性为0、1、2、3、4、5、6,我们可以通过利用这一点来使队头指针和队尾指针判断和进行某些操作时更方便。
以下给出判断循环队列为满队的判断条件:

if((Q.rear+1)%MaxSize==Q.front)
	return true;

直接通过代码(Q.rear+1)%MaxSize==Q.front可能无法理解【队尾指针加1取余等于队头指针】,以下会在入队和出队操作时细讲从而明白,这里的代码意思就是当队尾指针进1与MaxSize取余的值等于头指针时,此时队列已满,如下:
在这里插入图片描述
我们可知当前队头指针Q.front=1,Q.rear=0,即(Q.rear+1)%MaxSize=(0+1)%7=1%7=1,等于Q.front=1,所以此时队列为满队,此时队头指针在队尾指针的下一个位置。

(四)入队(插入操作)

前面已经规定入队时少使用一个队列单元,我们可以得出,每次入队操作时,也就是首先判断队列是否为满队,然后先送值到队列的末尾,然后再将队尾指针Q.rear加1取模(通过%实现),入队操作针对Q.rear。
在这里插入图片描述
执行一次入队操作,送数据元素的值“A”(从队尾插入,先进先出),队尾指针Q.rear移动加1,如下:
在这里插入图片描述
入队的代码依然是通过取余运算实现,队尾指针加1,即Q.rear=(Q.rear+1)%MaxSize,我们知道不管前面(Q.rear+1)为多少,它与MaxSize(图示中MaxSize=5)取余的结果只可能是0、1、2、3、4,也就是队尾指针Q.rear的每次移动加1。【入队操作只针对队尾指针,队尾指针加1取余

//入队(插入操作)
bool EnterQueue(SqQueue &Q,int x){
	if((Q.rear+1)%MaxSize==Q.front)	//若队列为满,则报错 
		return false;
	Q.data[Q.rear]=x;	//送入入队数据元素x的值 
	Q.rear=(Q.rear+1)%MaxSize;	//队尾指针加1取模 
	return true;
}

(五)出队(删除操作)

每次出队操作时,也就是首先判断队列是否为空队,然后先取队列的队头元素,然后再将队头指针Q.front加1取模(通过%实现),出队操作针对Q.front。
在这里插入图片描述
执行一次出队操作,先取队头元素“A”(A位于队头,先进先出),然后再队头指针Q.front移动加1,如下:
在这里插入图片描述
出队的代码依然是通过取余运算实现,队头指针加1,即Q.front=(Q.front+1)%MaxSize,我们知道不管前面(Q.front+1)为多少,它与MaxSize(图示中MaxSize=5)取余的结果只可能是0、1、2、3、4,也就是队头指针Q.front的每次移动加1。【出队操作只针对队头指针,队头指针加1取余

//出队(删除操作)
bool PopQueue(SqQueue &Q,int x) {
	if(Q.front==Q.rear)	//若队列为空,则报错 
		return false;
	x=Q.data[Q.front];	//取出队头数据元素x的值 
	Q.front=(Q.front+1)%MaxSize;	//队头指针加1取模 
	return true;
}

例如,若用数组A[0……5]来实现循环队列,且当前rear和front的值分别为1和5,当从队列中删除一个元素,再加入两个元素后,求此时rear和front的值。
在循环队列中,每删除一个元素,队头指针front=(front+1)%MaxSize,
即front=(5+1)%6=6%6=0;
每插入一个元素,队尾指针rear=(rear+1)%MaxSize,
加入一个元素后,rear=(1+1)%6=2%6=2,
再加入一个元素后,rear=(2+1)%6=3%6=3,
故最后rear和front的值分别为3和0。

(六)输出循环队列中的元素个数

首先判断队列是否为空,然后通过队尾指针减队头指针加上MaxSize的值与MaxSize取余,可得到数据元素个数,即(Q.rear-Q.front+MaxSize)%MaxSize,代码如下:

//循环队列的数据元素个数
bool NumQueue(SqQueue Q){
	if(Q.front==Q.rear)	//若队列为空,则报错 
		return false;
	int num=(Q.rear-Q.front+MaxSize)%MaxSize;
	printf("当前循环队列的数据元素个数为:%d\n",num);
}

例如,已知一个循环队列,其存储空间为数组data[21],front指向队头元素的前一个位置,rear指向队尾元素,假设当前front和rear的值分别为8和3,求当前队列的长度。
(rear-front+MaxSize)%MaxSize=(3-8+21)%21
=16%21
=16
故当前队列长度为16。

(七)读取循环队列队头元素

读取循环队列队头元素首先判断队列是否为空,然后也是通过取余操作,取当前队头指针Q.front所指的数据元素,代码如下:

//读取循环队列队头元素
bool GetHeadQueue(SqQueue Q,int &x) { 
	if(Q.front==Q.rear)	//若队列为空,则报错 
		return false;
	x=Q.data[(Q.front)%MaxSize];
	return true;
}

(八)循环队列的遍历输出

循环队列的遍历输出首先判断队列是否为空,然后与顺序队列的遍历输出代码一样,也是通过一个while循环,循环条件是当队头指针小于队尾指针时一直执行下去,然后每次取队头指针所指的数据元素,之后再加1,从而输出每个数据元素,代码如下:

//循环队列的遍历输出
bool PrintQueue(SqQueue Q,int x) {
	if(Q.front==Q.rear)	//若队列为空,则报错 
		return false;
	while(Q.front<Q.rear) {
		x=Q.data[Q.front++];
		printf("%d ",x);
	}
	return true;
}

(九)循环队列的建立

顺序队列的建立程序代码与顺序队列是一样的,通过输入一个值表示要创建的顺序队列的元素个数,然后通过一个for循环建立顺序队列,其中每次通过队列的入队操作,从而向队列中输入数据元素并插入至队列中,代码如下:

//循环队列的建立
void CreateQueue(SqQueue &Q,int x) {
	int number;
	printf("请输入要建立的队列元素个数:\n");
	scanf("%d",&number);
	for(int i=0; i<number; i++) {
		printf("输入第%d个入队的元素:\n",i+1);
		scanf("%d",&x);
		EnterQueue(Q,x);
	}
}

❤️循环队列的完整代码

循环队列的完整代码如下:

#include<stdio.h>
#define MaxSize 20
//循环队列的定义
typedef struct {
	int data[MaxSize];
	int front,rear;	//队头指针和队尾指针
} SqQueue;

//循环队列的初始化
void InitQueue(SqQueue &Q) {
	Q.front=Q.rear=0;	//队头指针和队尾指针d都指向0
}

//判断循环队列是否为空队
bool EmptyQueue(SqQueue Q){
	if(Q.front==Q.rear)	//当队头指针front等于队尾指针rear时队列为空 
		return true;
	else
		return false;
}

//判断循环队列是否为满队
bool FullQueue(SqQueue Q){
	if((Q.rear+1)%MaxSize==Q.front)
		return true;
	else
		return false;
}

//入队(插入操作)
bool EnterQueue(SqQueue &Q,int x){
	if((Q.rear+1)%MaxSize==Q.front)	//若队列为满,则报错 
		return false;
	Q.data[Q.rear]=x;	//送入入队数据元素x的值 
	Q.rear=(Q.rear+1)%MaxSize;	//队尾指针加1取模 
	return true;
}

//出队(删除操作)
bool PopQueue(SqQueue &Q,int x) {
	if(Q.front==Q.rear)	//若队列为空,则报错 
		return false;
	x=Q.data[Q.front];	//取出队头数据元素x的值 
	Q.front=(Q.front+1)%MaxSize;	//队头指针加1取模 
	return true;
}

//读取循环队列队头元素
bool GetHeadQueue(SqQueue Q,int &x) { 
	if(Q.front==Q.rear)	//若队列为空,则报错 
		return false;
	x=Q.data[(Q.front)%MaxSize];
	return true;
}

//循环队列的遍历输出
bool PrintQueue(SqQueue Q,int x) {
	if(Q.front==Q.rear)	//若队列为空,则报错 
		return false;
	while(Q.front<Q.rear) {
		x=Q.data[Q.front++];
		printf("%d ",x);
	}
	return true;
}

//循环队列的数据元素个数
bool NumQueue(SqQueue Q){
	if(Q.front==Q.rear)	//若队列为空,则报错 
		return false;
	int num=(Q.rear-Q.front+MaxSize)%MaxSize;
	printf("当前循环队列的数据元素个数为:%d\n",num);
}

//循环队列的建立
void CreateQueue(SqQueue &Q,int x) {
	int number;
	printf("请输入要建立的队列元素个数:\n");
	scanf("%d",&number);
	for(int i=0; i<number; i++) {
		printf("输入第%d个入队的元素:\n",i+1);
		scanf("%d",&x);
		EnterQueue(Q,x);
	}
}

一个简单的循环队列的基本实现例子

例如,创建一个循环队列,其初始数据为{1,5,7,3},首先遍历输出该队列,然后读取队头元素,此时输出当前循环队列的数据元素个数,执行一次入队操作,入队元素为{6},此时读取当前的队头元素,执行一次出队操作,此时再读取当前的队头元素,最后遍历输出当前队列。

代码如下:

#include<stdio.h>
#define MaxSize 20
//循环队列的定义
typedef struct {
	int data[MaxSize];
	int front,rear;	//队头指针和队尾指针
} SqQueue;

//循环队列的初始化
void InitQueue(SqQueue &Q) {
	Q.front=Q.rear=0;	//队头指针和队尾指针d都指向0
}

//判断循环队列是否为空队
bool EmptyQueue(SqQueue Q){
	if(Q.front==Q.rear)	//当队头指针front等于队尾指针rear时队列为空 
		return true;
	else
		return false;
}

//判断循环队列是否为满队
bool FullQueue(SqQueue Q){
	if((Q.rear+1)%MaxSize==Q.front)
		return true;
	else
		return false;
}

//入队(插入操作)
bool EnterQueue(SqQueue &Q,int x){
	if((Q.rear+1)%MaxSize==Q.front)	//若队列为满,则报错 
		return false;
	Q.data[Q.rear]=x;	//送入入队数据元素x的值 
	Q.rear=(Q.rear+1)%MaxSize;	//队尾指针加1取模 
	return true;
}

//出队(删除操作)
bool PopQueue(SqQueue &Q,int x) {
	if(Q.front==Q.rear)	//若队列为空,则报错 
		return false;
	x=Q.data[Q.front];	//取出队头数据元素x的值 
	Q.front=(Q.front+1)%MaxSize;	//队头指针加1取模 
	return true;
}

//读取循环队列队头元素
bool GetHeadQueue(SqQueue Q,int &x) { 
	if(Q.front==Q.rear)	//若队列为空,则报错 
		return false;
	x=Q.data[(Q.front)%MaxSize];
	return true;
}

//循环队列的遍历输出
bool PrintQueue(SqQueue Q,int x) {
	if(Q.front==Q.rear)	//若队列为空,则报错 
		return false;
	while(Q.front<Q.rear) {
		x=Q.data[Q.front++];
		printf("%d ",x);
	}
	return true;
}

//循环队列的数据元素个数
bool NumQueue(SqQueue Q){
	if(Q.front==Q.rear)	//若队列为空,则报错 
		return false;
	int num=(Q.rear-Q.front+MaxSize)%MaxSize;
	printf("当前循环队列的数据元素个数为:%d\n",num);
}

//循环队列的建立
void CreateQueue(SqQueue &Q,int x) {
	int number;
	printf("请输入要建立的队列元素个数:\n");
	scanf("%d",&number);
	for(int i=0; i<number; i++) {
		printf("输入第%d个入队的元素:\n",i+1);
		scanf("%d",&x);
		EnterQueue(Q,x);
	}
}

//主函数
int main() {
	SqQueue Q;
	int x,e;
	InitQueue(Q);	//初始化
	CreateQueue(Q,x);	//创建队列
	printf("遍历输出当前队列元素:\n");
	PrintQueue(Q,x);	//遍历输出队列
	printf("\n");
	GetHeadQueue(Q,x);	//读取队列的队头元素
	printf("读取队列的队头元素,当前队头元素为:%d\n",x);
	NumQueue(Q);
	printf("请输入一个要入队的元素:");
	scanf("%d",&e);
	EnterQueue(Q,e);	//入队操作 
	GetHeadQueue(Q,x);
	printf("读取队列的队头元素,当前队头元素为:%d\n",x);
	NumQueue(Q);
	PopQueue(Q,x);	//出队操作 
	GetHeadQueue(Q,x);
	printf("执行一次出队操作后,当前队头元素为:%d\n",x);
	printf("遍历输出当前队列元素:\n");
	PrintQueue(Q,x);	//遍历输出队列
}

运行结果如下:
在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐