【数据结构】 队列 离散事件模拟 银行排队问题
1.问题介绍
假设银行有四个窗口对外接待客户,从早晨开门起不断有客户进入
银行每个窗口每个时刻只能接待一个客户,因此人数过多时需在每个窗口进行排队
现在编制一个程序模型银行的业务活动,并计算一天中客户在银行逗留平均时间
2.问题分析
该问题可以分为几个事件,1.客户到达事件 2.客户离开事件(四个窗口)
按事件发生的先后顺序进行处理
所有的事件可以使用一个有序链表存储,事件的插入按发生时间进行排序
四个窗口中排队可以用四个队列表示
3.基本思路
各种类型的定义
客户:客户是队列中的数据元素,包括 到达时间,办理业务所需时间
事件:事件是有序链表中的数据元素,包括 事件发生时间 事件类型(到达事件 ,四个窗口离开事件)
事件的定义:
到达事件:每次客户到达产生两个随机数,一个表示该客户需要办理业务的时间 ,另一个表示下一个客户达到的间隔时间
假设第一个客户到达的时间是0,这样便实现了客户的不间断到达。
客户达到后需要找到最短的队列,然后将该客户的到达时间和处理时间插入队列
如果该队列的长度为1,设定一个离开事件并插入事件表
离开事件:
首先确定客户离开的是哪一个队列,删除其队头元素
计算客户逗留时间
如果该队列非空,设定下一个离开事件并插入事件表
模拟结果:
设定每个客户逗留时间不超过三十分钟,两个客户到达时间相隔不超过八分钟,设置营业时间为600分钟,运行结果如下:
平均逗留时间为27.1173分钟。
上述为书本中的基本思路,但与实际情况肯定有较大出入, 一个明显的问题是:没有更换队列的操作,
倘若某一队列中的人处理事务时间较短,则会造成某个时间一个队列为空,其余队列有两人以上在排队的情况,这在现实中显然是难以发生的
解决方法是在离开事件中加入判断,倘若该队列为空且其余队列有两人以上排队,进行更换队列操作。
更换队列:
1.找到需要更换的客户所在队列
2.将客户转移到空队列
3.设定该队列的一个离开事件
优化后模拟结果
使用同样参数,运行优化后程序,客户平均逗留时间为23.61分钟,有较大提升,且与实际预测情况一致
附加部分代码博客最后。
核心代码:
初始化
void OpenForDay() {
//初始化操作
TotalTime = 0; CustomerNum = 0; //初始化累计事4件和客户数为 0
InitList(ev); //初始事件链表为空表
en.OccurTime = 0; en.NType = 0; //设定第一个客户到达事件
ListInsert_L(ev, 1, en); //插入事件表
for (int i = 1; i <= 4; i++)
{
InitQueue(q[i]); //初始化队列
}
}//OpenForDay
客户到达事件:
void CustomerArrived() {
//处理客户到达事件 en.Type = 0.
++CustomerNum;
int durtime, intertime;
durtime = rand() % 30; //客户处理事务时间
intertime = rand() % 8; //下一个客户到达事件间隔
int t = en.OccurTime + intertime; //下一个客户到达时刻
if (t < CloseTime)
OrderInsert(ev, {
t,0 }, cmp);
int i = Minmum(q); //找到最短队列
EnQueue(q[i], {
en.OccurTime, durtime }); //客户进入队列i
if (QueueLenth(q[i]) == 1)
OrderInsert(ev,{
en.OccurTime + durtime, i}, cmp); //队列长度为1时,设定一个离开事件
}//CustomerArrived
客户离开事件
void CustomerDeparture() {
//处理客户离开事件,en.NTpye>0
QElemType customer ;
int i = en.NType;
DeQueue(q[i], customer); //删除第i队列的排头客户
TotalTime += en.OccurTime - customer.ArrivaTime;//累计客户逗留时间
if (!QueueEmpty(q[i])) {
GetHead(q[i], customer);
OrderInsert(ev, {
en.OccurTime + customer.Duration,i }, cmp);//设定第i队列的离开事件
}
}//CustomerDeparture
模拟过程
void Bank_Simulation() {
OpenForDay();
while (!ListEmpty(ev)) {
Link p;
DelFirst(GetHead(ev), p); en = GetCurElem(p);
if (en.NType == 0) {
CustomerArrived(); //处理客户到达事件
cout << p->data.OccurTime << " 有客户到达" << endl;
}
else {
CustomerDeparture();
cout << p->data.OccurTime << " 有客户离开" << endl;
} //处理客户离开事件
}
cout << (float)TotalTime / CustomerNum;
}//Bank_Simulation
其余代码
LinkQueue代码见 队列的基本操作
只需修改 QElemType 数据类型
typedef struct {
int ArrivaTime; //到达时刻
int Duration; //办理事务所需要时间
}QElemType;