银行事件模拟程序
问题简述:
假设某银行有 4 个窗口接待客户,从早晨银行开门起不断有客户进入银行。由于每个窗口在某个时刻只能接待一个客户,因此在客户人多的时候,需要在每个窗口前顺次排队。对于刚进入银行的客户,如果某个窗口的业务员正空闲,则可以上前办理业务;反之,若 4 个窗口均有客户,他便会排在人数最少的队列后面。现需要编制一个程序以模拟银行的这种业务活动并计算一天中客户在银行逗留的时间。
思考:
(1)需处理两类事件:客户到达事件,客户离开事件。
客户到达事件发生的时刻随客户到来自然形成;客户离开事件由客户事务所需时间和等待所耗时间决定。
由于程序驱动是按事件发生时刻的先后顺序进行,则事件表应是有序表,其主要操作是插入和删除事件。
(2)由于银行有 4 个窗口,因此程序需要 4 个队列,队列中有关客户的主要信息是客户到达的时刻和客户办理事务所需的时间。
以下,我们随机产生下一个客户到达时刻距离此刻客户到达相差的时间,这个用户办理事务所需时间也随机产生,这两个随机时间都是此时刻客户到达时产生。由此,客户到达事件的驱动因素就是上一个客户到达(即可以插入下一个客户到达事件)。
客户离开事件的驱动因素有两个:一个是,客户一到达,就有窗口空闲,则已经可以计算客户离开时间,插入客户离开事件。第二个是,一个客户离开,则他所在队伍的下一个客户可以开始办理事务,则已经可以计算下一个客户离开时间,插入客户离开事件。
/* 银行业务模拟。实现算法3.6、3.7的程序 */
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */
#include<malloc.h> /* malloc()等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<process.h> /* exit() */
/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
#define Qu 4 /* 客户队列数 */
#define Khjg 5 /* 两相邻到达的客户的时间间隔最大值 */
#define Blsj 30 /* 每个客户办理业务的时间最大值 */
typedef struct /* 定义ElemType为结构体类型 */
{
int OccurTime; /* 事件发生时刻 */
int NType; /* 事件类型,Qu表示到达事件,0至Qu-1表示Qu个窗口的离开事件 */
}Event, ElemType; /* 事件类型,有序链表LinkList的数据元素类型 */
/* ----------------------- 带头结点的线性链表类型 ----------------------------*/
typedef struct LNode /* 结点类型 */
{
ElemType data;
struct LNode *next;
}LNode, *Link, *Position;
typedef struct LinkList /* 链表类型 */
{
Link head, tail; /* 分别指向线性链表中的头结点和最后一个结点 */
int len; /* 指示线性链表中数据元素的个数 */
}LinkList;
/* ------------------------------------------------------------------------------------------*/
typedef LinkList EventList; /* 事件链表类型,定义为有序链表 */
typedef struct
{
int ArrivalTime; /* 到达时刻 */
int Duration; /* 办理事务所需时间 */
}QElemType; /* 定义QElemType(队列的数据元素类型)为结构体类型; */
/* ----------------------- 单链队列--队列的链式存储结构 ----------------------------*/
typedef struct QNode
{
QElemType data;
struct QNode *next;
}QNode, *QueuePtr;
typedef struct
{
QueuePtr front, rear; /* 队头、队尾指针 */
}LinkQueue;
/* ------------------------------------------------------------------------------------------*/
/* --------------------------- 需要用到的具有实用意义的线性链表操作 --------------------------*/
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;
}
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; /* 链表空 */
}
ElemType GetCurElem(Link p)
{ /* 已知p指向线性链表中的一个结点,返回p所指结点中数据元素的值 */
return p->data;
}
Status ListEmpty(LinkList L)
{ /* 若线性链表L为空表,则返回TRUE,否则返回FALSE */
if (L.len)
return FALSE;
else
return TRUE;
}
Position GetHead(LinkList L)
{ /* 返回线性链表L中头结点的位置 */
return L.head;
}
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;
}
/* -------------------------------- 需要用到的链队列操作 -------------------------------*/
Status InitQueue(LinkQueue *Q)
{ /* 构造一个空队列Q */
(*Q).front = (*Q).rear = (QueuePtr)malloc(sizeof(QNode));
if (!(*Q).front)
exit(OVERFLOW);
(*Q).front->next = NULL;
return OK;
}
Status QueueEmpty(LinkQueue Q)
{ /* 若Q为空队列,则返回TRUE,否则返回FALSE */
if (Q.front == Q.rear)
return TRUE;
else
return FALSE;
}
int QueueLength(LinkQueue Q)
{ /* 求队列的长度 */
int i = 0;
QueuePtr p;
p = Q.front;
while (Q.rear != p)
{
i++;
p = p->next;
}
return i;
}
Status GetHead_Q(LinkQueue Q, QElemType *e) /* 避免与bo2-6.c重名 */
{ /* 若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR */
QueuePtr p;
if (Q.front == Q.rear)
return ERROR;
p = Q.front->next;
*e = p->data;
return OK;
}
Status EnQueue(LinkQueue *Q, QElemType e)
{ /* 插入元素e为Q的新的队尾元素 */
QueuePtr 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)
{ /* 若队列不空,删除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;
}
/* ------------------------------------------------------------------------------------------*/
/* 程序中用到的主要变量(全局)。算法3.7 */
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);
}
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]);
}
void Random(int *d, int *i)
{
*d = rand() % Blsj + 1; /* 1到Blsj之间的随机数 */
*i = rand() % Khjg + 1; /* 1到Khjg之间的随机数 */
}
int Minimum(LinkQueue Q[]) /* 返回最短队列的序号 */
{
int l[Qu];
int 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;
}
return k;
}
void CustomerArrived()
{ /* 处理客户到达事件,en.NType=Qu */
QElemType f;
int durtime, intertime, i;
++CustomerNum;
Random(&durtime, &intertime); /* 生成随机数 */
et.OccurTime = en.OccurTime + 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队列的一个离开事件并插入事件表 */
}
}
void CustomerDeparture()
{ /* 处理客户离开事件,en.NTyPe<Qu */
int i;
i = en.NType;
DeQueue(&q[i], &customer); /* 删除第i队列的排头客户 */
TotalTime += en.OccurTime - customer.ArrivalTime; /* 累计客户逗留时间 */
if (!QueueEmpty(q[i]))
{ /* 设定第i队列的一个离开事件并插入事件表 */
GetHead_Q(q[i], &customer);
et.OccurTime = en.OccurTime + customer.Duration;
et.NType = i;
OrderInsert(&ev, et, cmp);
}
}
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(); /* 处理客户离开事件 */
} /* 计算并输出平均逗留时间 */
printf("顾客总数:%d, 所有顾客共耗时:%d分钟, 平均每人耗时: %d分钟\n", CustomerNum, TotalTime, TotalTime / CustomerNum);
}
void main()
{
printf("请输入银行营业时间长度(单位:分)\n");
scanf("%d", &CloseTime);
Bank_Simulation();
}
运行结果: