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 顺序存储的循环队列的优缺点
优点
- 空间利用率高:由于循环队列的数组可以循环利用,因此可以充分利用数组空间。
- 读写效率高:循环队列的入队和出队操作只需修改队头和队尾指针,不需要移动元素,因此操作效率高。
- 操作简单:相对于链式存储结构,循环队列顺序存储结构的实现更为简单,容易理解和实现。
缺点
- 容量有限:由于循环队列的数组是固定大小的,因此队列的容量有限。当队列已满时,无法再插入元素,需要进行扩容操作。
- 可浪费空间:当循环队列中出现大量的插入和删除操作时,会导致队列头尾指针之间的空间无法利用,造成空间浪费。
- 实现复杂:在实现循环队列时,需要注意队头和队尾指针的移动,以及循环条件的处理,相对于简单的顺序队列,实现略微复杂。
下一节,会实现链式存储的循环队列,来解决队列容量有限的问题和空间浪费的问题。