队列&循环队列
队列:先进先出的线性表。当我们讨论队列的顺序存储实现时,我们需要为它分配一个数组空间。以及两枚指针,Q.front和Q.rear。其中Q.rear指向队尾,也就是我们插入元素的位置;Q.front指向队头,也就是我们删除元素的位置。每当我们完成一次插入或删除操作后,Q.rear或Q.front都需要加1,也就是向后移动一格,指向下一个可插入或可删除元素的位置。
我们将会面临的问题:假溢出
左图是队列刚刚初始化后的样子,没有元素,两枚指针均指向数组下标为0的位置,表示此时队列为空。此时这个队列可以插入元素但不可删除元素,且插入的元素位于Q.rear指向的位置。
中图是插入三个元素且还没有删除元素时的样子。这时下一个可插入元素的位置位于Q.rear指向的位置(也就是数组下标为3的位置),下一个可删除元素的位置(事实上是第一个元素,因为还没有删除元素)位于Q.front指向的位置(也就是数组下标为0的位置)。
右图是在中图的基础上再插入三个元素且删除了两个元素时的样子。这时仍然可以删除元素,但已经不能插入元素了,因为这时Q.rear指向的下标已经达到数组的MAXSIZE了(也就是溢出了)。但是我们显然可以发现,数组中仍有未被利用的空间,也就是删除了两个元素后空出的空间,我们将这种情况称为“假溢出”。
我们使用循环队列来解决“假溢出”问题。
循环队列可以达到这样的效果:在上面的右图中,在插入元素F后,指针Q.rear指向数组下标为0的位置。也就是说,在数组“未满”的条件下,Q.rear和Q.front指针在数组内循环。类似于循环链表那样,数组的最后一个格子的下一个格子是数组的第一个格子。
循环队列:为什么我要对上面的“未满”一词加引号呢?这是因为为了达到循环队列的效果,我们需要对循环队列的“队满”状态做新的定义。
首先,我们仍然定义“Q.front==Q.rear”为队空的时的状态。有两种情况会达到这种状态,第一种情况是刚刚初始化循环队列后,Q.front和Q.rear都指向数组的第一个格子,这时我们称这个循环队列是空的。第二种情况是在删除完某个元素后,队列中不再有元素,这时这个队列显然是空的。如下图所示,在删除元素D后,Q.front将会追上Q.rear,于是他们两个相等,这时我们称这个循环队列是空的。显然有且只有这两种情况使得队空,因此用“Q.front==Q.rear”判断队空是合理的。
但是我们会发现,如果不改变“队满”的定义,也就是“Q.rear==MAXSIZE”的定义,循环队列永远达不到“满”的状态。这是因为在循环队列中,当指针Q.rear指向数组下标为MAXSIZE-1后,它的下一步会回到0,于是它永远会小于MAXSIZE。
然而在改变“队满”的定义之前,我们需要先改变进行插入和删除元素后两枚指针的变化。为了使得两枚指针在达到MAXSIZE-1后回到0,我们将原来的操作(也就是直接加1,然后两枚指针分别指向下一个格子)改为“加1后对MAXSIZE取模”,也就是Q.rear=(Q.rear+1)%MAXSIZE,Q.front=(Q.front+1)%MAXSIZE。这样的改变可以使得两枚指针在数组内循环。
接下来我们会发现这种情况,当所有空间都有元素,它达到了“队满”的状态,可是这时Q.front==Q.rear,也就是此时队列明明是满的,计算机却会认为它是空的。如下图所示,在插入元素C后,事实上队满,但两枚指针相遇,于是计算机认为队空。
为了解决这个问题,我们需要牺牲一个格子,也就是人为使得两枚指针永远不能因为队满而相遇。因此,我们对循环队列队满的定义是“(Q.rear+1)%MAXSIZE==Q.front”。这个定义是说:如果因为我插入了这个元素使得两枚指针相遇,那么这个时候队列是满的,我这个插入操作是不合法的。
总结:为了解决普通队列的“假溢出问题”,我们使用了循环队列。
普通队列和循环队列的各种操作和状态的判断条件是不一致的,下面分别给出了他们各自的判断条件。
普通队列:①插入元素后:Q.rear++;②删除元素后:Q.front++;
③队空:Q.front==Q.rera;④队满:Q.rear==MAXSIZE;⑤队长:Q.rear-Q.front。
循环队列:①插入元素后:Q.rear=(Q.rear+1)%MAXSIZE;
②删除元素后:Q.front=(Q.front+1)%MAXSIZE;
③队空:Q.front==Q.rera;④队满:(Q.rear+1)%MAXSIZE==Q.front;
⑤队长:(Q.rear+MAXSIZE-Q.front)%MAXSIZE
以下是循环队列的基本操作的代码实现,以及SCAU8584的解决方案,以及SCAU8590的两种解决思路。
//此项目功能:完成循环队列的基本操作及SCAU8584、SCAU8590
#include <stdio.h>
#include <malloc.h>
#define QElemType int
#define Status int
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXQSIZE 100
typedef struct
{
QElemType *base;
int front;
int rear;
} SqQueue;
Status InitQueue(SqQueue &Q)
{
Q.base = new QElemType[MAXQSIZE];
Q.front = 0;
Q.rear = 0;
return OK;
}
Status DestroyQueue(SqQueue &Q)
{
delete Q.base;
Q.front = 0;
Q.rear = 0;
return OK;
}
Status ClearQueue(SqQueue &Q)
{
Q.rear = Q.front;
return OK;
}
Status QueueEmpty(SqQueue Q)
{
if (Q.front == Q.rear)
return TRUE;
else
return FALSE;
}
int QueueLength(SqQueue Q)
{
return (Q.rear + MAXQSIZE - Q.front) % MAXQSIZE;
}
Status GetHead(SqQueue Q, QElemType &e)
{
if (Q.rear == Q.front)
return ERROR;
else
e = Q.base[Q.front];
return OK;
}
Status EnQueue(SqQueue &Q, QElemType e)
{
if ((Q.rear + 1) % MAXQSIZE == Q.front)
return ERROR;
else
{
Q.base[Q.rear] = e;
Q.rear = (Q.rear + 1) % MAXQSIZE;
}
return OK;
}
Status DeQueue(SqQueue &Q, QElemType &e)
{
if (Q.rear == Q.front)
return ERROR;
else
{
e = Q.base[Q.front];
Q.front = (Q.front + 1) % MAXQSIZE;
}
return OK;
}
Status QueueTraverse(SqQueue Q)
{
if (Q.front == Q.rear)
printf("The Queue is Empty!");
else
{
int i = Q.front;
printf("The Queue is: ");
while (i != Q.rear)
{
printf("%d ", Q.base[i]);
i++;
i = i % MAXQSIZE;
}
}
printf("\n");
return OK;
}
int main()
{
/*SCAU 8584
int a;
SqQueue S;
QElemType x, e;
if(InitQueue(S))
{
printf("A Queue Has Created.\n");
}
while(1)
{
printf("1:Enter \n2:Delete \n3:Get the Front \n4:Return the Length of the Queue\n5:Load the Queue\n0:Exit\nPlease choose:\n");
scanf("%d",&a);
switch(a)
{
case 1: scanf("%d", &x);
if(!EnQueue(S,x)) printf("Enter Error!\n");
else printf("The Element %d is Successfully Entered!\n", x);
break;
case 2: if(!DeQueue(S,e)) printf("Delete Error!\n");
else printf("The Element %d is Successfully Deleted!\n", e);
break;
case 3: if(!GetHead(S,e))printf("Get Head Error!\n");
else printf("The Head of the Queue is %d!\n", e);
break;
case 4: printf("The Length of the Queue is %d!\n",QueueLength(S));
break;
case 5: QueueTraverse(S);
break;
case 0: return 1;
}
}
*/
/*SCAU 8590 队列算法
SqQueue Q;
InitQueue(Q);
int number_of_people;
scanf("%d",&number_of_people);
float Time[number_of_people][2];
int ArriveTime,WorkTime;
for(int i=0;i<number_of_people;i++) //数组Time第一列记录客户到达时间,第二列记录客户办理时间
{
scanf("%d %d",&ArriveTime,&WorkTime);
Time[i][0]=ArriveTime;
Time[i][1]=WorkTime;
}
float LastFinishTime=Time[0][0]; //从第一位客户到的时候开始计时
float WaitTime=0;
for(int i=0;i<number_of_people;i++) EnQueue(Q,Time[i][1]);
for(int i=0;i<number_of_people;i++)
{
DeQueue(Q,WorkTime);
if(Time[i][0]<LastFinishTime) //如果在前一名客户完成之前到达,需要等到前一名客户完成
{
WaitTime+=LastFinishTime-Time[i][0]; //等待的时间即是上一个完成的时间减去自己到的时间
LastFinishTime+=WorkTime; //完成的时间等于上一个完成的时间加上自己花费的时间,因为上一个完成之后自己就紧跟着办理业务
}
else LastFinishTime=Time[i][0]+WorkTime; //不需要等待时,完成的时间等于自己到的时间加上自己办理业务所花费的时间
}
printf("%.2f",WaitTime/number_of_people);
*/
/*SCAU 8590 一般算法
int number_of_people;
scanf("%d",&number_of_people);
float ArriveTime,WorkTime,WaitTime=0;
float Time[number_of_people][3];
for(int i=0;i<number_of_people;i++) //数组Time第一列记录客户到达时间,第二列记录客户办理时间,第三列记录客户完成时间。
{
scanf("%f %f",&ArriveTime,&WorkTime);
Time[i][0]=ArriveTime;
Time[i][1]=WorkTime;
}
Time[0][2]=Time[0][0]+Time[0][1]; //第一位客户完成办理的时间是他到达的时间加上他需要办理的时间
for(int i=1;i<number_of_people;i++)
{
if(Time[i][0]<=Time[i-1][2]) Time[i][2]=Time[i-1][2]+Time[i][1]; //如果在前一名客户完成之前到达,那么该客户完成办理的时间是前一名客户完成的时间加上他需要办理的时间
else Time[i][2]=Time[i][0]+Time[i][1]; //如果在前一名客户完成之后到达,那么该客户完成办理的时间是他到达的时间加上他需要办理的时间
}
for(int i=1;i<number_of_people;i++) //显然第一位客户不需要等待
{
if(Time[i][0]>=Time[i-1][2]); //如果在前一名客户完成之前到达,需要等到前一名客户完成;反之则不需要等待。
else WaitTime+=Time[i-1][2]-Time[i][0];
}
printf("%.2f",WaitTime/number_of_people);
*/
return 0;
}
欢迎讨论交流!