循环队列的顺序存储结构

5.1 循环队列的顺序存储结构

5.1.1 循环队列的简介


队列这种结构就像我们日常排队一样,是一种’‘先进先出’'的结构,队头出,队尾进。下图是一个简单的队列结构:

在这里插入图片描述

但是对于这种数组队列,出队两个元素后,想在队尾在加入元素,会出现数组越界,称之为"假溢出"的现象,因为我们出队后前面还有两个空间(如下图):

在这里插入图片描述


所以就引入了循环队列,其实就是将数组队列的首尾相连,让队尾指针可以移动到前面出队后空闲的位置。

下面是涉及到有关循环队列操作的表:

操作函数操作结果
InitQueue(SqQueue *Q)初始化队列
ClearQueue(SqQueue *Q)清空队列
QueueEmpty(SqQueue Q)判断队列是否为空
QueueLength(SqQueue Q)返回队列长度
GetHead(SqQueue Q,QElemType *e)获取队头元素
EnQueue(SqQueue *Q,QElemType e)入队
DeQueue(SqQueue *Q,QElemType *e)出队,并将数据存入指针e
QueueTraverse(SqQueue Q)遍历队列




5.1.2 循环队列顺序结构定义


循环队列需要包含一个队头指针front,一个队尾指针rear,还有存储数据的数组data,如下图结构:

在这里插入图片描述

注意,若队列不空,这边将队尾指针指向队尾元素下一个位置。代码如下:

typedef struct{
    QElemType data[MAXSIZE];
    int front;
    int rear;		//队列不空,将rear指针指向队尾元素下一个元素
}SqQueue;




5.1.3 初始化队列和清空队列

初始化队列和清空队列其实是两个操作几乎一样,都是生成一个空的队列,如下图:

在这里插入图片描述


1.初始化队列

/*初始化队列*/
Status InitQueue(SqQueue *Q){
    Q->front = 0;
    Q->rear = 0;
    return OK;
}


2.清空队列

/*清空队列*/
Status ClearQueue(SqQueue *Q){
    Q->front=Q->rear=0;
    return OK;
}




5.1.4 判断队列是否为空和获取队列长度

1.判断队列是否为空



需要判断队列是否为空,只需要判断队头指针front,和队尾指针rear是否指向同一位置,即front==rear,就可以判断是空队列,代码如下:

/*判断队列是否为空*/
Status QueueEmpty(SqQueue Q){
    if(Q.front==Q.rear)
        return TRUE;
    else
        return FALSE;
}


2.获取队列长度

因为这是一个循环队列,肯定会出现rear值小于front值的情况,所以我们不能单纯的用rear-front去求队列长度,首先看下面这种情况:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FBaRhFW4-1680411815502)(C:\Users\Cookie\AppData\Roaming\Typora\typora-user-images\image-20230402122514340.png)]

是一种rear<front的情况,我们要求他的长度,先简单数一下,长度为4,这是如何计算出来的呢?

公式是(rear-front+MAXSIZE)%MAXSIZE,MAXSIZE是数组最大长度,这对于rear<front的情况也是适用的,我们将上图的情况代入公式得:(1-3+6)%6=4,长度就是4。

代码如下:

/*返回队列长度*/
int QueueLength(SqQueue Q){
    return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}




5.1.5 获得队头元素

实现这个操作,只需要利用队头指针即可,只需要注意判断队空的情况,这里直接给出代码:

/*得到队头元素 存入e*/
Status GetHead(SqQueue Q,QElemType *e){
    if(Q.front==Q.rear)		//队空
        return ERROR;
    *e = Q.data[Q.front];
    return OK;
}




5.1.6 入队和出队


1.入队

入队之前我们要判断队列是否满了,队列满有下面两种情况:

在这里插入图片描述

  • 第一种情况:rear<front,当rear+1==front时队满
  • 第二种情况:rear>front,当(rear+1)%MAXSIZE==front时队满

综合两种情况,都可以使用(rear+1)%MAXSIZE==front来判断队是否满。

入队则只需要将元素从队尾加入到空闲位置,然后将队尾指针向后移动一位rear=(rear+1)%MAXSIZE

代码如下:

/*入队操作*/
Status EnQueue(SqQueue *Q,QElemType e){
    if( (Q->rear+1)%MAXSIZE == Q->front )    //判断队满
        return ERROR;
    Q->data[Q->rear] = e;
    Q->rear = (Q->rear+1)%MAXSIZE;          //rear指针向后移动 若到最后则到数组头部
    return OK;
}


2.出队

出队之前要判断,队列是否为空,然后再将队头元素出队,并将数据赋值给指针e,最后将队头指针移到下一个位置,如下图:

在这里插入图片描述

代码如下:

/*出队操作*/
Status DeQueue(SqQueue *Q,QElemType *e){
    if(Q->front == Q->rear)         //判断队空
        return ERROR;
    
    *e = Q->data[Q->front];
    Q->front = (Q->front+1)%MAXSIZE; //出队后 front向后移一位
}




5.1.7 遍历和打印队列

只要不到队尾一直输出队列中元素就可以实现。

代码如下:

/*遍历队列*/
Status visit(QElemType c){
    printf("%d ",c);
    return OK;
}

Status QueueTraverse(SqQueue Q){
    int i = Q.front;
    while(i!=Q.rear){
        visit(Q.data[i]);
        i = (i+1)%MAXSIZE;
    }
    printf("\n");
    return OK;
}



5.1.8 测试

测试代码如下:

int main(){
	Status j;
	int i=0,l;
	QElemType d;
	SqQueue Q;
	InitQueue(&Q);
	printf("初始化队列后,队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));

	printf("请输入整型队列元素(不超过%d个),-1为提前结束符: ",MAXSIZE-1);
	do
	{
		/* scanf("%d",&d); */
		d=i+100;
		if(d==-1)
			break;
		i++;
		EnQueue(&Q,d);
	}while(i<MAXSIZE-1);

	printf("队列长度为: %d\n",QueueLength(Q));
	printf("现在队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
	printf("连续%d次由队头删除元素,队尾插入元素:\n",MAXSIZE);
	for(l=1;l<=MAXSIZE;l++)
	{
		DeQueue(&Q,&d);
		printf("删除的元素是%d,插入的元素:%d \n",d,l+1000);
		/* scanf("%d",&d); */
		d=l+1000;
		EnQueue(&Q,d);
	}
	l=QueueLength(Q);

	printf("现在队列中的元素为: \n");
	QueueTraverse(Q);
	printf("共向队尾插入了%d个元素\n",i+MAXSIZE);
	if(l-2>0)
		printf("现在由队头删除%d个元素:\n",l-2);
	while(QueueLength(Q)>2)
	{
		DeQueue(&Q,&d);
		printf("删除的元素值为%d\n",d);
	}

	j=GetHead(Q,&d);
	if(j)
		printf("现在队头元素为: %d\n",d);
	ClearQueue(&Q);
	printf("清空队列后, 队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));

    system("pause");
	return 0;
}



运行结果:

初始化队列后,队列空否?1(1:空 0:否)
请输入整型队列元素(不超过19个),-1为提前结束符: 队列长度为: 19
现在队列空否?0(1:空 0:否)
连续20次由队头删除元素,队尾插入元素:
删除的元素是100,插入的元素:1001
删除的元素是101,插入的元素:1002
删除的元素是102,插入的元素:1003
删除的元素是103,插入的元素:1004
删除的元素是104,插入的元素:1005
删除的元素是105,插入的元素:1006
删除的元素是106,插入的元素:1007
删除的元素是107,插入的元素:1008
删除的元素是108,插入的元素:1009
删除的元素是109,插入的元素:1010
删除的元素是110,插入的元素:1011
删除的元素是111,插入的元素:1012
删除的元素是112,插入的元素:1013
删除的元素是113,插入的元素:1014
删除的元素是114,插入的元素:1015
删除的元素是115,插入的元素:1016
删除的元素是116,插入的元素:1017
删除的元素是117,插入的元素:1018
删除的元素是118,插入的元素:1019
删除的元素是1001,插入的元素:1020
现在队列中的元素为:
1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
共向队尾插入了39个元素
现在由队头删除17个元素:
删除的元素值为1002
删除的元素值为1003
删除的元素值为1004
删除的元素值为1005
删除的元素值为1006
删除的元素值为1007
删除的元素值为1008
删除的元素值为1009
删除的元素值为1010
删除的元素值为1011
删除的元素值为1012
删除的元素值为1013
删除的元素值为1014
删除的元素值为1015
删除的元素值为1016
删除的元素值为1017
删除的元素值为1018
现在队头元素为: 1019
清空队列后, 队列空否?1(1:空 0:否)
请按任意键继续. . .



5.1.9 完整代码



这边附上完整代码:

#include<stdio.h>
#include<stdlib.h>

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20

typedef int Status;
typedef int QElemType;


typedef struct{
    QElemType data[MAXSIZE];
    int front;
    int rear;
}SqQueue;


/*初始化队列*/
Status InitQueue(SqQueue *Q){
    Q->front = 0;
    Q->rear = 0;
    return OK;
}

/*清空队列*/
Status ClearQueue(SqQueue *Q){
    Q->front=Q->rear=0;
    return OK;
}

/*判断队列是否为空*/
Status QueueEmpty(SqQueue Q){
    if(Q.front==Q.rear)
        return TRUE;
    else
        return FALSE;
}


/*返回队列长度*/
int QueueLength(SqQueue Q){
    return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}

/*得到队头元素 存入e*/
Status GetHead(SqQueue Q,QElemType *e){
    if(Q.front==Q.rear)		//队空
        return ERROR;
    *e = Q.data[Q.front];
    return OK;
}

/*入队操作*/
Status EnQueue(SqQueue *Q,QElemType e){
    if( (Q->rear+1)%MAXSIZE == Q->front )    //判断队满
        return ERROR;
    Q->data[Q->rear] = e;
    Q->rear = (Q->rear+1)%MAXSIZE;          //rear指针向后移动 若到最后则到数组头部
    return OK;
}


/*出队操作*/
Status DeQueue(SqQueue *Q,QElemType *e){
    if(Q->front == Q->rear)         //判断队空
        return ERROR;
    
    *e = Q->data[Q->front];
    Q->front = (Q->front+1)%MAXSIZE; //出队后 front向后移一位
}

/*遍历队列*/
Status visit(QElemType c){
    printf("%d ",c);
    return OK;
}

Status QueueTraverse(SqQueue Q){
    int i = Q.front;
    while(i!=Q.rear){
        visit(Q.data[i]);
        i = (i+1)%MAXSIZE;
    }
    printf("\n");
    return OK;
}

int main(){
	Status j;
	int i=0,l;
	QElemType d;
	SqQueue Q;
	InitQueue(&Q);
	printf("初始化队列后,队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));

	printf("请输入整型队列元素(不超过%d个),-1为提前结束符: ",MAXSIZE-1);
	do
	{
		/* scanf("%d",&d); */
		d=i+100;
		if(d==-1)
			break;
		i++;
		EnQueue(&Q,d);
	}while(i<MAXSIZE-1);

	printf("队列长度为: %d\n",QueueLength(Q));
	printf("现在队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
	printf("连续%d次由队头删除元素,队尾插入元素:\n",MAXSIZE);
	for(l=1;l<=MAXSIZE;l++)
	{
		DeQueue(&Q,&d);
		printf("删除的元素是%d,插入的元素:%d \n",d,l+1000);
		/* scanf("%d",&d); */
		d=l+1000;
		EnQueue(&Q,d);
	}
	l=QueueLength(Q);

	printf("现在队列中的元素为: \n");
	QueueTraverse(Q);
	printf("共向队尾插入了%d个元素\n",i+MAXSIZE);
	if(l-2>0)
		printf("现在由队头删除%d个元素:\n",l-2);
	while(QueueLength(Q)>2)
	{
		DeQueue(&Q,&d);
		printf("删除的元素值为%d\n",d);
	}

	j=GetHead(Q,&d);
	if(j)
		printf("现在队头元素为: %d\n",d);
	ClearQueue(&Q);
	printf("清空队列后, 队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));

    system("pause");
	return 0;
}




5.1.10 顺序存储的循环队列的优缺点

优点

  1. 空间利用率高:由于循环队列的数组可以循环利用,因此可以充分利用数组空间。
  2. 读写效率高:循环队列的入队和出队操作只需修改队头和队尾指针,不需要移动元素,因此操作效率高。
  3. 操作简单:相对于链式存储结构,循环队列顺序存储结构的实现更为简单,容易理解和实现。

缺点

  1. 容量有限:由于循环队列的数组是固定大小的,因此队列的容量有限。当队列已满时,无法再插入元素,需要进行扩容操作。
  2. 可浪费空间:当循环队列中出现大量的插入和删除操作时,会导致队列头尾指针之间的空间无法利用,造成空间浪费。
  3. 实现复杂:在实现循环队列时,需要注意队头和队尾指针的移动,以及循环条件的处理,相对于简单的顺序队列,实现略微复杂。




下一节,会实现链式存储的循环队列,来解决队列容量有限的问题和空间浪费的问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

压力小子呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值