问题描述
假设某银行有N个窗口对外接待客户,从银行开门起不断有客户进入银行,若有窗口空闲,则直接办理业务,反之,则排在人数最少的窗口后面。编写一个程序来模拟银行业务并计算客户在银行逗留的平均时间。
解题思路
(1)要想实现这个算法,我们需要两个很重要的数据结构,一个是需要事件链表,事件链表就是用来描述事件发生的情况的一个链表。每个窗口都需要一些客户在排队,因此我们需要N个队列构成队列数组。
(2)定义客户结构体,包括客户到达银行的时间和办理业务所需要的时间。
(3)在银行排列算法中,要采取链式队列,为什么不用循环队列呢?因为在银行排队的过程中,你无法确定排队人数的多少,队列的长度预先不可知,采用链队列更合适。
(4)由于事件表需要按照事件发生的先后顺序排列,需要经常进行插入操作,则也采用单链表做存储结构。每个结点包括两个数据域;occurtime和ntype(分别表示事件发生的时间和事件的类型,-1表示的是新用户,0~3表示的是客户离开1~4个窗口)。
(5)我们需要定义一个链表处理类,来完成银行事件的处理流程,这个链表处理类是一个有序链表,最多有N+1个结点,分别对应新到达客户,和N个不同的窗口队列的离开事件。
(6)银行排队模拟处理的结束条件:也就是后续的客户的到达时间超过了银行关门时间,则不再输入随机数(不再加入新客户),只是继续处理(删除)事件链表中的事件,直到事件链表的结点都处理完了,则银行排队模拟过程结束。此时计算银行排队总时间,以及客户平均服务时间,并输出即可。
(7)链表中的数据域数据结构类型是定义的QElemType,分别包含客户到达时间和办理业务所需要的时间,这两者相加就能得到这位顾客离开的时间。
(8)队列中每个元素标识排队等候的客户,队尾元素为最迟进入银行的客户,而队头元素则表示正在被银行业务员接待的客户,只有下面几种情况会促使队列发生变化;一种情况是新的客户进入银行,他将加入元素最少的队列而成为该队列新的队尾元素。还有就是某个正在被业务员接待的客户办理完业务离开银行。
在这个问题中,我们需要处理的是事件,事件的主要信息是事件类型和事件发生的时间。需要处理的事件有两类,一类是客户到达事件,一类是客户离开事件。客户到达事件发生的时刻由客户到达自然发生,客户离开事件的发生时刻由客户办理业务的时间和等待时间决定,这些事件是有序的,因此我们选择的数据结构是有序表。
在模拟程序中需要的另一种数据结构是表示客户排队的队列,队列中有关客户的主要信息是客户的到达时刻和客户办理业务所需要的时间。每个队列的队头元素是正在窗口办理业务的客户,他办完业务离开队列的时刻就是即将发生的客户离开事件的时刻,所以对每个队头客户都存在一个即将发生的客户离开事件,所以在任何时刻即将发生的事件只有(1)新的客户到达;(2)1~N号窗口客户离开。对此,我们选择的数据结构是链队列,为什么不采取循环队列呢?因为客户的数目无法确定。
typedef struct{
int OccurTime;//事件发生时刻
int NType;//事件类型,0表示到达事件,1到N表示N个窗口的离开事件
}Event,ElemType;//事件类型,有序链表LinkList的数据类型
typedef struct{
int ArrivalTime;//到达时刻
int Duration;//办理事务所需时间
}QElemType;//队列的数据元素类型
typedef struct node{
ElemType data;
struct node *next;
}EventNode,*EventList;
功能实现
(1)比较N个队列中的个数,将新到的客户加入到元素个数最少的队列使其成为新的队尾元素。若该队列原本就是空的,则刚刚插进去的元素也是队头元素,此时应该设定一个事件——刚进入银行的客户办理完业务离开银行的事件,并插入事件表。因为队头元素可以通过客户结点的到达银行的时间+办理业务所需要的时间=离开时间,得到这位客户离开事件中所需要的时间部分;而如果不是队头元素,不知道其他队列中加上本队列中有多少事件要在前面执行,所以暂时还不能添加离开事件,等排到了就能添加了。
(2)设定一个新事件,如果下一个客户即将到达银行,就插入事件表。如果最早发生的事件是某个队列中的客户离开银行,则模拟程序需要做的工作是:①该客户出列,并计算他在银行逗留时间。②当该队列非空时,计算新的队头元素将离开银行的时间,并由此设立一个新的离开事件,并插入事件表。
(3)银行逗留的平均时间=所有客户在银行逗留时间的和/客户人数。
事件算法运行时某个状态
初始化:
第一次循环:
第二次循环:
第三次循环:
第四次循环:
第五次循环:
第六次循环:
第七次循环:
基本操作
EventList InitList()操作结果:构建一个空的事件链表
void displist(EventList ev)操作结果:打印事件链表
void OrderInsert(EventList ev,Event en)操作结果:将新生成客户到达事件插入到事件链表ev中
void OpenForDay()操作结果:开门函数,也就是初始化操作
void Random(int *durtime,int *intevatime)操作结果:产生两个随机数
Int rand() 操作结果:产生随机数种子,并返回值
int Mini(LinkQueue q[])操作结果:得到人数最少的队列并返回值
void CustomerArrived()操作结果:处理客户到达事件
void CustomerDeparture()操作结果:处理客户离开事件
银行业务模拟
系统模块结构设计
主要步骤的实现
新客户到达事件的处理
我们的程序会产生两个随机数,一个是此时刻到达的客户办理业务所需要的时间durtime,第二个是下一客户将到达的时间间隔intertime,假设当前事件发生的时刻为occurtime,则下一个客户到达事件发生的时间应该为occurtime+intertime。由此应该产生一个新的客户到达事件插入事件表,刚到达的客户则应该插入到当前所含元素最少的队列当中,若该队列在插入前为空,则还应该产生一个客户离开事件插入事件表。
客户离开事件的处理
首先计算该客户在银行逗留的时间,然后从队列中移除该客户后查看队列是否为空,若不为空则设定一个新的队头客户离开事件。计算客户的逗留时间=当前时间-客户到达时间,如果当前窗口还有人,开始处理下一位,记录事件的发生时间,i号窗口开始处理该客户,处理完后插入到事件链表中等待离队。
系统设计与实现
流程图
(一)开门函数OpenForDay()
分析:开门函数也就是初始化操作,在初始化操作中,需要初始化累计时间和客户数为0,初始化事件链表为空表,银行一开门就预计有下一个用户到来,设定第一个客户地到来事件,插入事件表,置空队列,也就是初始化银行窗口。
void OpenForDay()
{//开门函数,也就是初始化操作
int i;
TotalTime=0;
CustomerNum=0;//初始化累计时间和客户数为0
ev=InitList();//初始化事件链表为空表 ,银行一开门就预计有下一个用户到来
en.OccurTime=0;
en.NType=0;//设定第一个客户到达事件
OrderInsert(ev,en);//插入事件表
for(i=1;i<=N;i++)
InitQueue(&q[i]);//置空队列 ,初始化银行窗口
}
(二)客户到达事件 CustomerArrived()
分析:设定银行有客户进入,预计下一个客户到达时间,如果用户来的时候银行还没关门,则将这个用户到达插入事件表,去找人数最少的队列,插入人数最少的队伍办理业务,如果说某个队列只有一个客户在办理业务,预计该客户的离开时间,插入离开事件表,办理完之后让他离开。
void CustomerArrived()
{//顾客到达函数
int durtime,intertime,i,j;
QElemType qet;
Event temp;//离开事件
CustomerNum++;//客户数量加1
Random(&durtime,&intertime);//生成随机数
temp.OccurTime=en.OccurTime+intertime;//下一个客户到达时间
temp.NType=0;
if(temp.OccurTime<CLOSETIME)//用户来的时候还没有关门
OrderInsert(ev,temp);//插入事件表
i=Mini(q);//去找人数最少的队列
qet.ArrivalTime=en.OccurTime;
qet.Duration=durtime;
EnQueue(&q[i],qet);//插入人数最少的队伍
printf("%dmin:A customer arrived insert to windows %d!\n",en.OccurTime,i);
for(j=1;j<=N;j++)
printf("windows %d number:%d\n",j,QueueLength(q[j]));
if(QueueLength(q[i])==1)
{//一个队列只有一个客户在办理事件
temp.NType=i;
temp.OccurTime=en.OccurTime+durtime;//预计离开时间
OrderInsert(ev,temp);//设定第i队列的一个离开事件并插入离开事件表,让这个人离开
}
}
(三)客户离开事件CustomerDeparture()
分析:客户离开事件就是删除第i队列的排头客户,计算客户的逗留时间=当前时间-客户到达时间,如果当前窗口还有人,开始处理下一位客户,记录事件的发生时间,i号窗口开始处理该客户,处理完了就插入到事件链表中等待离队。
void CustomerDeparture()
{//顾客离开函数
int i,j;
QElemType qet;
Event temp;
i=en.NType;//窗口号
DeQueue(&q[i],&customer);//删除第i队列的排头用户
TotalTime+=en.OccurTime-customer.ArrivalTime;//累计客户的逗留时间 =当前时间-客户到达时间
if(!QueueEmpty(q[i]))//当前窗口还有人
{
customer=q[i].front->next->data;//开始处理下一位
temp.OccurTime=en.OccurTime+customer.Duration;//事件的发生时间
temp.NType=i;//i号窗口开始处理该客户
OrderInsert(ev,temp);//插入到事件链表中等待离队
}
printf("%dmin:A customer left from windows %d\n",en.OccurTime,i);
for(j=1;j<=N;j++)
printf("windows %d number:%d\n",j,QueueLength(q[j]));
}
(四)得到客户数最少的队列Mini()
int Mini(LinkQueue q[])
{//得到人数最少的队列
int min=QueueLength(q[1]),i,j=1,len;
for(i=2;i<=N;i++)
{
len=QueueLength(q[i]);
if(min>len)
{
min=len;
j=i;
}
}
return j;
}
(五)插入事件OrderInsert()
void OrderInsert(EventList ev,Event en)
{//按occurtime从小到大的顺序插入
EventList p=ev->next,q=ev,r;
while(p&&p->data.OccurTime<en.OccurTime)//找到事件发生时间所在事件链表中的位置
{
q=p;
p=p->next;
}
r=(EventList) malloc(sizeof(EventNode));
r->data.OccurTime=en.OccurTime;
r->data.NType=en.NType;
r->next=p;
q->next=r;
}
(六)随机数函数
void Random(int *durtime,int *intevatime)
{
*durtime=rand()%DURTIME+1;
*intevatime=rand()%INTERTIME+1;
}
int rand()
{
int k=(int)(((double)seed/32767)*10000)%29;
seed=(13107*seed+6553)%32767;
return k>0?k:-k;
}
(七)事件初始化
EventList InitList()
{
EventList p;
p=(EventList) malloc(sizeof(EventNode));
if(!p) exit(ERROR);
p->next=NULL;
return p;
}
(八)打印函数
void displist(EventList ev)
{
EventList p=ev->next;
while(p)
{
printf("occurtime:%d type:%d\n",p->data.OccurTime,p->data.NType);
p=p->next;
}
//getchar();
}
代码实现
银行业务模拟代码
//离散事件模拟
#include "LQUEUE.C"
#include "stdio.h"
#define N 4
#define DURTIME 30
#define INTERTIME 5
#define CLOSETIME 200
typedef struct{
int OccurTime;//事件发生时刻
int NType;//事件类型,0表示到达事件,1到N表示N个窗口的离开事件
}Event,ElemType;//事件类型,有序链表LinkList的数据类型
typedef struct node{
ElemType data;
struct node *next;
}EventNode,*EventList;
EventList ev;//事件链表头指针
Event en;//表示一个事件
LinkQueue q[N+1];//N个客户队列
QElemType customer;//客户记录
long TotalTime,CustomerNum;//累计客户逗留时间,客户数
int seed=300;
void displist(EventList ev)
{
EventList p=ev->next;
while(p)
{
printf("occurtime:%d type:%d\n",p->data.OccurTime,p->data.NType);
p=p->next;
}
//getchar();
}
EventList InitList()
{
EventList p;
p=(EventList) malloc(sizeof(EventNode));
if(!p) exit(ERROR);
p->next=NULL;
return p;
}
void OrderInsert(EventList ev,Event en)
{//按occurtime从小到大的顺序插入
EventList p=ev->next,q=ev,r;
while(p&&p->data.OccurTime<en.OccurTime)//找到事件发生时间所在事件链表中的位置
{
q=p;
p=p->next;
}
r=(EventList) malloc(sizeof(EventNode));
r->data.OccurTime=en.OccurTime;
r->data.NType=en.NType;
r->next=p;
q->next=r;
}
void OpenForDay()
{//开门函数,也就是初始化操作
int i;
TotalTime=0;
CustomerNum=0;//初始化累计时间和客户数 为0
ev=InitList();//初始化事件链表为空表 ,银行一开门就预计有下一个用户到来
en.OccurTime=0;
en.NType=0;//设定第一个客户到达事件
OrderInsert(ev,en);//插入事件表
for(i=1;i<=N;i++)
InitQueue(&q[i]);//置空队列 ,初始化银行窗口
}
void Random(int *durtime,int *intevatime)
{
*durtime=rand()%DURTIME+1;
*intevatime=rand()%INTERTIME+1;
}
int rand()
{
int k=(int)(((double)seed/32767)*10000)%29;
seed=(13107*seed+6553)%32767;
return k>0?k:-k;
}
int Mini(LinkQueue q[])
{//得到人数最少的队列
int min=QueueLength(q[1]),i,j=1,len;
for(i=2;i<=N;i++)
{
len=QueueLength(q[i]);
if(min>len)
{
min=len;
j=i;
}
}
return j;
}
void CustomerArrived()
{//顾客到达函数
int durtime,intertime,i,j;
QElemType qet;
Event temp;//离开事件
CustomerNum++;//客户数量加1
Random(&durtime,&intertime);//生成随机数
temp.OccurTime=en.OccurTime+intertime;//下一个客户到达时间
temp.NType=0;
if(temp.OccurTime<CLOSETIME)//用户来的时候还没有关门
OrderInsert(ev,temp);//插入事件表
i=Mini(q);//去找人数最少的队列
qet.ArrivalTime=en.OccurTime;
qet.Duration=durtime;
EnQueue(&q[i],qet);//插入人数最少的队伍
printf("%dmin:A customer arrived insert to windows %d!\n",en.OccurTime,i);
for(j=1;j<=N;j++)
printf("windows %d number:%d\n",j,QueueLength(q[j]));
if(QueueLength(q[i])==1)
{//一个队列只有一个客户在办理事件
temp.NType=i;
temp.OccurTime=en.OccurTime+durtime;//预计离开时间
OrderInsert(ev,temp);//设定第i队列的一个离开事件并插入离开事件表,让这个人离开
}
}
void CustomerDeparture()
{//顾客离开函数
int i,j;
QElemType qet;
Event temp;
i=en.NType;//窗口号
DeQueue(&q[i],&customer);//删除第i队列的排头用户
TotalTime+=en.OccurTime-customer.ArrivalTime;//累计客户的逗留时间 =当前时间-客户到达时间
if(!QueueEmpty(q[i]))//当前窗口还有人
{
customer=q[i].front->next->data;//开始处理下一位
temp.OccurTime=en.OccurTime+customer.Duration;//事件的发生时间
temp.NType=i;//i号窗口开始处理该客户
OrderInsert(ev,temp);//插入到事件链表中等待离队
}
printf("%dmin:A customer left from windows %d\n",en.OccurTime,i);
for(j=1;j<=N;j++)
printf("windows %d number:%d\n",j,QueueLength(q[j]));
}
int main()
{
EventList p;
OpenForDay();
p=ev->next;
while(p)
{
en.OccurTime=p->data.OccurTime;
en.NType=p->data.NType;
ev->next=p->next;
free(p);
if(en.NType==0)
CustomerArrived();
else
CustomerDeparture();
// displist(ev);
p=ev->next;
}
printf("客户平均等待时间为:%f\n",(float)TotalTime/CustomerNum);
return 0;
}
LQUEUE.C
#include "my.h"
#include<stdlib.h>
//typedef int QElemType;
typedef struct{
int ArrivalTime;
int Duration;
}QElemType;
typedef struct QNode{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct{
QueuePtr front;
QueuePtr rear;
}LinkQueue;
Status InitQueue(LinkQueue *Q)
{
Q->front=Q->rear=(QueuePtr) malloc (sizeof(QNode));
if(!Q->front) exit(OVERFLOW);
Q->front->next=NULL;
return OK;
}
Status DestroyQueue(LinkQueue *Q)
{
while(Q->front)
{
Q->rear=Q->front->next;
free(Q->front);
Q->front=Q->rear;
}
return OK;
}
Status EnQueue(LinkQueue *Q,QElemType e)
{
QueuePtr p;
p=(QueuePtr)malloc(sizeof(QNode));
if(!p) exit(OVERFLOW);
p->data=e;
p->next=NULL;
Q->rear->next=p;
Q->rear=p;
return OK;
}
Status DeQueue(LinkQueue *Q,QElemType *e)
{
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;
}
int QueueLength(LinkQueue Q)
{
int n=0;
QueuePtr p=Q.front;
while(p!=Q.rear)
{
n++;
p=p->next;
}
return n;
}
Status QueueEmpty(LinkQueue Q)
{
if(Q.front==Q.rear) return TRUE;
else return FALSE;
}