相当于一个时间轴,但是管着四个队列的进出情况。
第个队列的人员进入时,都要预报下一个人员进来的时刻,并登记下一个人员进来时刻到时间轴,同时将自己进来的时刻及逗留时间选择一个最短的队列进行注册,如果自己是第一个进来的,或者可以排到某一队列的第一位,则还要在时间轴上登记出的时间。
离开时,自己将注册的队列信息删除,同时,当前队列的下一个人向时间轴预报自己离开的时间。
第个队列的人员进入时,都要预报下一个人员进来的时刻,并登记下一个人员进来时刻到时间轴,同时将自己进来的时刻及逗留时间选择一个最短的队列进行注册,如果自己是第一个进来的,或者可以排到某一队列的第一位,则还要在时间轴上登记出的时间。
离开时,自己将注册的队列信息删除,同时,当前队列的下一个人向时间轴预报自己离开的时间。
可见,时间轴就是一个事件(无论是来了的,还是走了的)集,通过预判事件的先后发生顺序而控制着整个过程的发展。
一、问题描述及解决思想
1、问题描述
2、解决思想
我们需要注意“事件”的概念,客户到达和客户离开都称为“事件”。
二、C语言描述
三、C语言实现
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#define Qu 4 //客户队列数
#define Khjg 5 //两相邻到达的客户的时间间隔最大值
#define Blsj 30//每个客户办理业务的时间最大值
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
typedef int Boolean;
typedef struct
{
int OccurTime; //事件发生时刻
int NType; //事件类型,Qu表示到达事件,
//0-Qu-1表示Qu个窗口的离开事件
}Event,ElemType;//事件类型,有序链表LinkList的数据元类型
typedef struct LNode // 结点类型
{
ElemType data;
LNode *next;
}*Link,*Position;
struct LinkList // 链表类型
{
Link head,tail; // 分别指向线性链表中的头结点和最后一个结点
int len; // 指示线性链表中数据元素的个数
};
typedef LinkList EventList; // 事件链表类型,定义为有序链表
typedef struct
{
int ArrivalTime;
int Duration;
}QElemType; //队列的数据元素类型
typedef struct QNode
{
QElemType data;
QNode *next;
}*QueuePtr; //队列
struct LinkQueue
{
QueuePtr front,rear; // 队头、队尾指针
};
EventList ev;//事件表
Event en; //事件
Event et;//临时变量
LinkQueue q[Qu]; //Qu个队列客户
QElemType customer; //客户记录
int TotalTime=0,CustomerNum=0;//累计客户逗留时间,客户数(初值为0)
int CloseTime; // 银行营业时间(单位是分)
int cmp(Event a,Event b)
{ // 依事件a的发生时刻<、=或>事件b的发生时刻分别返回-1、0或1
if(a.OccurTime==b.OccurTime)
return 0;
else
return (a.OccurTime-b.OccurTime)/abs(a.OccurTime-b.OccurTime);
}
Status InitList(LinkList &L)
{ // 构造一个空的线性链表
Link p;
p=(Link)malloc(sizeof(LNode)); // 生成头结点
if(p)
{
p->next=NULL;
L.head=L.tail=p;
L.len=0;
return OK;
}
else
return ERROR;
}//InitList
Status InitQueue(LinkQueue &Q)
{ // 构造一个空队列Q
if(!(Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode))))
exit(-1);
Q.front->next=NULL;
return OK;
}//InitQueue
Status OrderInsert(LinkList &L,ElemType e,int (*comp)(ElemType,ElemType))
{ // 已知L为有序线性链表,将元素e按非降序插入在L中。(用于一元多项式)
Link o,p,q;
q=L.head;
p=q->next;
while(p!=NULL&&comp(p->data,e)<0) // p不是表尾且元素值小于e
{
q=p;
p=p->next;
}
o=(Link)malloc(sizeof(LNode)); // 生成结点
o->data=e; // 赋值
q->next=o; // 插入
o->next=p;
L.len++; // 表长加1
if(!p) // 插在表尾
L.tail=o; // 修改尾结点
return OK;
}//OrderInsert
void OpenForDay()
{
int i;
InitList(ev);
en.OccurTime=0;
en.NType=Qu;
OrderInsert(ev,en,cmp);//插入事件表
for(i=0;i<Qu;++i)
InitQueue(q[i]);
}//OpenForDay
void Random(int &d,int &i)
{
d=rand()%Blsj+1;//1到Blsj之间的随机数
i=rand()%Khjg+1;
}//Random
int QueueLength(LinkQueue Q)
{ // 求队列的长度
int i=0;
QueuePtr p;
p=Q.front;
while(Q.rear!=p)
{
i++;
p=p->next;
}
return i;
}//QueueLength
int Minimum(LinkQueue Q[]) //返回最短队列的序号
{
int l[Qu],i,k;
for(i=0;i<Qu;i++)
l[i]=QueueLength(Q[i]);
k=0;
for(i=1;i<Qu;i++)
if(l[i]<l[0])
{
l[0]=l[i];
k=i;
}//if
return k;
}//Minimum
Status EnQueue(LinkQueue &Q,QElemType e)
{ // 插入元素e为Q的新的队尾元素
QueuePtr p;
if(!(p=(QueuePtr)malloc(sizeof(QNode)))) // 存储分配失败
exit(OVERFLOW);
p->data=e;
p->next=NULL;
Q.rear->next=p;
Q.rear=p;
return OK;
}//EnQueue
Status DeQueue(LinkQueue &Q,QElemType &e)
{ // 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR
QueuePtr p;
if(Q.front==Q.rear)
return ERROR;
p=Q.front->next;
e=p->data;
Q.front->next=p->next;
if(Q.rear==p)
Q.rear=Q.front;
free(p);
return OK;
}//DeQueue
void CustomerArrived()
{
QElemType f;
int durtime,intertime,i;
++CustomerNum;
Random(durtime,intertime);
et.OccurTime=en.OccurTime+intertime; //下一客户到达时刻,隔了intertime来了下一//个客户
et.NType=Qu; // 队列中只有一个客户到达事件
if(et.OccurTime<CloseTime)
OrderInsert(ev,et,cmp); //不让下一个进入营业厅了
i=Minimum(q); //处理当前进来的人
f.ArrivalTime=en.OccurTime;
f.Duration=durtime;
EnQueue(q[i],f);
if(QueueLength(q[i])==1)
{
et.OccurTime=en.OccurTime+durtime;
et.NType=i;
OrderInsert(ev,et,cmp); // 设定第i队列的一个离开事件并插入事件表
}//if
}//CustomerArrived
Status GetHead(LinkQueue Q,QElemType &e)
{ // 若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR
QueuePtr p;
if(Q.front==Q.rear)
return ERROR;
p=Q.front->next;
e=p->data;
return OK;
}//GetHead
Position GetHead(LinkList L)
{ // 返回线性链表L中头结点的位置
return L.head;
}//GetHead
Status QueueEmpty(LinkQueue Q)
{ // 若Q为空队列,则返回TRUE,否则返回FALSE
if(Q.front==Q.rear)
return TRUE;
else
return FALSE;
}//QueueEmpty
void CustomerDeparture()
{ // 处理客户离开事件,en.NType<Qu
int i;
i=en.NType;
DeQueue(q[i],customer);
TotalTime+=en.OccurTime-customer.ArrivalTime;
if(!QueueEmpty(q[i])) //设定第i队列的一个离开事件,并插入事件表
{
GetHead(q[i],customer);
et.OccurTime=en.OccurTime+customer.Duration;
et.NType=i;
OrderInsert(ev,et,cmp);
}//if
}//CustomerDeparture
Status ListEmpty(LinkList L)
{ // 若线性链表L为空表,则返回TRUE,否则返回FALSE
if(L.len)
return FALSE;
else
return TRUE;
}//ListEmpty
Status DelFirst(LinkList &L,Link h,Link &q) // 形参增加L,因为需修改L
{ // h指向L的一个结点,把h当做头结点,删除链表中的第一个结点并以q返回。
// 若链表为空(h指向尾结点),q=NULL,返回FALSE
q=h->next;
if(q) // 链表非空
{
h->next=q->next;
if(!h->next) // 删除尾结点
L.tail=h; // 修改尾指针
L.len--;
return OK;
}
else
return FALSE; // 链表空
}//DelFirst
ElemType GetCurElem(Link p)
{ // 已知p指向线性链表中的一个结点,返回p所指结点中数据元素的值
return p->data;
}//GetCurElem
void Bank_Simulation()
{
Link p;
OpenForDay();
while(!ListEmpty(ev))
{
DelFirst(ev,GetHead(ev),p);
en.OccurTime=GetCurElem(p).OccurTime;
en.NType=GetCurElem(p).NType;
if(en.NType==Qu)
CustomerArrived();
else
CustomerDeparture();
}//while
printf("顾客总数:%d, 所有顾客共耗时:%d分钟, 平均每人耗时: %d分钟\n",CustomerNum,TotalTime,TotalTime/CustomerNum);
}//Bank_Simulation
int main()
{
printf("请输入银行营业时间长度(单位:分)\n");
scanf("%d",&CloseTime);
Bank_Simulation();
return OK;
}
四、举例
五、说明