数据结构与算法——实验1:银行VIP窗口设计
实验要求
模拟银行
实验内容:
基于课本p46给出的代码,实现以下功能。
1.VIP窗口窗口
在原有代码基础上添加VIP窗口功能,VIP窗口的出纳员只服务VIP用户,当VIP用户到达后可以
在本窗口按照到达先后顺序等待。
第1题约定
约定1号出纳员所在的1号窗口为VIP窗口,且银行中只有一个VIP窗口。
约定普通用户只能在普通窗口排队等候,其选择窗口的优先级为:空普通窗口>非空普通窗口。
约定用户排队过程中选定窗口后就会一直在该队列中等待,不会更换排队队伍。
约定用户之间无法感知对方是处理何种业务,不知道其业务所需的具体时间和剩余时间。
约定模拟时间外不产生用户到达事件,但是银行将会处理完所有用户到达事件。
第一问(10分)
第一问约定
约定VIP用户可以在普通窗口和VIP窗口排队等候,其选择窗口的优先级为:空VIP窗口>空普通窗口>非空VIP窗口>非空普通窗口。
本小题第一问将在去掉到达时间与服务时间随机化的情况下,对同学们的代码进行正确性验证。
验证内容是增设VIP窗口对服务情况产生的影响。
请同学们按照如下提示修改代码:
// event.h
enum isVip {notVip, Vip};
struct event
{
...
isVip isvip;
...
};
// event.h
InitEvent(...)
CompareEvent(...)
// ...
...
以下文所示输入为例,时间处于0、4、8时分别到达一名随机身份的顾客,银行将安排窗口为顾
客服务。对于8时到来的VIP用户,他所面临的的情况:在增设VIP窗口前,不区分顾客身份服
务,8时到来的顾客面对两个非空串口的情况下将会选择随机选择一个窗口进行服务,VIP身份
没有获得收益;在增设VIP窗口后,VIP用户在某些情况下可以比普通用户更快受到服务。
#用例1
#./bank
Enter the simulation time in minutes: 10
Enter the number of bank tellers: 2
Enter the range of arrival times in minutes: 4 4
Enter the range of service times in minutes: 9 9
分析下表(繁忙程度=实际服务时间/总服务时间):
到达到达队列情况列情况 总用用时 1号窗口繁忙程度号窗口繁忙程度 2号窗口繁忙程度号窗口繁忙程度
P、P、V(增设VIP窗口前)
P、P、V(增设VIP窗口后)
第二问(30分)
第二问约定
约定VIP用户可以在VIP窗口排队等候,或者在普通窗口队列中插队(即排到普通窗口队列最前面)。
约定插队的VIP用户在普通窗口中按照到达顺序先后进行排队。
约定VIP用户选择窗口的优先级为:空VIP窗口>空普通窗口>插队到普通窗口队列。
编程实现功能,不给出测试用例。
2.VIP限时等待
第2题约定
约定只有出纳员服务完当前用户后,才会重新计算所有排队的用户中的VIP用户的等待时间,如果等待时间已到达最长等待时间,将优先服务等待时间长的VIP用户。
约定等待时间满的VIP顾客,可以更换柜台服务。即所有柜台出纳员会按照等待时间由长到短的顺序服务所有队列中的VIP用户。(对于VIP用户而言,打破最初不能更换队伍的约定)
无VIP窗口情况下,银行规定,VIP用户等待时间不得超过X分钟,VIP用户与普通用户之间设置排队策略。
请同学们按照如下提示修改代码,使得VIP用户最多等待waitHigh时间。注意:出纳员只需要照顾本队列中的VIP用户。
printf("Enter the longest waitting time the customer can tolerate in minutes: ");
scanf("%d", &s->waitHigh);
第一问((40分)分)
实现功能
3.最佳排队机制(20分,酌情加分)
第3题约定
提示:如何决定三种等待时间的重要性?如何赋予不同的权重?如何在不同的权重设置下实现适宜的排队机制?请根据实验数据进行情况讨论。
本题为开放性思考题。
综合考虑各类角色等待时间:被VIP插队的用户越多,普通用户越不满;VIP等待的时间越长,VIP越不满;有人排队但有窗口闲置时,银行不满。
请通过实验数据说明如何设置排队机制可能是最优的。
建议:参考题目设置,逐步放开随机化的元素进行分析,可站在用户的角度或者银行的角度说明。
(请认真作答,自圆其说,合乎情理即可)
实验要求
阅读课本p49-p54页代码解析
按照实验内容完成1, 2题
思考并通过实验的形式完成第3题
ps:
收拾硬盘的时候无意间看到了这个曾经折磨我到半夜三点多的实验,出于让新人们参考的目的,在此将代码开源。(请叫我雷锋(✿◡‿◡))
本实验在原有代码基础上,增加了VIP窗口功能,VIP限时等待功能等,在本题目的条件中有较好的实现效果。
其中random.h对大部分注释做了翻译处理,在Event类里添加了vipIffo,以标志是否为VIP客户。在Simulation类里添加了用于随机产生VIP编号的函数Randomvip,对RunSimulation函数进行了大量的修改,以实现VIP的功能。
针对VIP窗口问题
1.创建事件(Event)对象时,将VIP客户的标志vipIffo传入事件对象(Event)中;
2.获取下一个空闲的柜台号时,对于VIP客户,会优先选择VIP专用柜员,即获取当前最早完成服务的VIP柜员编号;
3.对于VIP顾客,更新VIP柜员开始服务时间和完成服务时间,并生成离开事件;同时,在一般柜员队列中进一步寻找可用柜员。
针对VIP限时等待
Requeue 函数可以实现对于等待时间长且VIP属性为0的顾客重新加入队列,并重置其柜台号和等待时间。
针对最佳排队机制
这个部分就自己发挥吧~
综合考虑各类角色等待时间:被VIP插队的用户越多,普通用户越不满;VIP等待的时间越长,VIP越不满;有人排队但有窗口闲置时,银行不满。
综合以上部分考虑,可以设置VIP用户在所有用户中的比例,并按照比例设置相应数量的VIP窗口。
代码写得比较烂,但是绝对能跑,混个80分应该不是问题,建议还是自己改改。
代码仅供参考
random.h
#ifndef RANDOM_NUMBER_GENERATOR
#define RANDOM_NUMBER_GENERATOR
#include <time.h>
// 宏定义常量,用于生成随机数和更新种子
const unsigned long maxshort = 65536L;
const unsigned long multiplier = 1194211693L;
const unsigned long adder = 12345L;
class RandomNumber
{
private:
// 私有成员,保存当前的种子
unsigned long randSeed;
public:
// 构造函数,如果不提供种子,则使用系统时间作为种子
RandomNumber(unsigned long s = 0);
// 生成随机整数,0 <= value <= n-1
unsigned short Random(unsigned long n);
// 生成随机实数,0 <= value < 1.0
double fRandom(void);
};
// 构造函数,用于初始化随机数种子
RandomNumber::RandomNumber(unsigned long s)
{
if (s == 0)
randSeed = time(0); // 自动使用系统时间作为种子
else
randSeed = s; // 使用用户提供的种子
}
// 生成指定范围内的随机整数
unsigned short RandomNumber::Random(unsigned long n)
{
randSeed = multiplier * randSeed + adder; // 用乘法和加法生成下一个随机数
return (unsigned short)((randSeed >> 16) % n); // 返回生成的随机数
}
// 生成 0 到 1 之间的随机实数
double RandomNumber::fRandom(void)
{
return Random(maxshort) / double(maxshort); // 生成 0 到 maxshort-1 的随机整数,然后除以 maxshort 得到实数
}
#endif
apqueue.h
#ifndef PRIORITYQUEUE_CLASS
#define PRIORITYQUEUE_CLASS
#include <iostream>
#include <stdlib.h>
using namespace std;
// maximum size of the priority queue array
const int MaxPQSize = 50;
class PQueue
{
private:
// priority queue array and count
int count;
DataType pqlist[MaxPQSize];
public:
// constructor
PQueue(void);
// priority queue modification operations
void PQInsert(const DataType& item);
DataType PQDelete(void);
void ClearPQ(void);
// priority queue test methods
int Requeue(int ID, int time, int NT, int servicetime);// here
int PQEmpty(void) const;
int PQFull(void) const;
int PQLength(void) const;
};
// initialize priority queue count
PQueue::PQueue(void) : count(0)
{}
// insert item into the priority queue
void PQueue::PQInsert(const DataType& item)
{
// if all elements of pqlist are used, terminate the program
if (count == MaxPQSize)
{
cerr << "Priority queue overflow!" << endl;
exit(1);
}
// place item at the rear of the list and increment count
pqlist[count] = item;
count++;
}
// delete an element from the priority queue and return its value
DataType PQueue::PQDelete(void)
{
DataType min;
int i, minindex = 0;
if (count > 0)
{
// find the minimum value and its index in pqlist
min = pqlist[0]; // assume pqlist[0] is the minimum
// visit remaining elments, updating minimum and index
for (i = 1; i < count; i++)
if (pqlist[i] < min)
{
// new minimum is pqlist[i]. new minindex is i
min = pqlist[i];
minindex = i;
}
// move rear element to minindex and decrement count
pqlist[minindex] = pqlist[count - 1];
count--;
}
// qlist is empty, terminate the program
else
{
cerr << "Deleting from an empty priority queue!" << endl;
exit(1);
}
// return minimum value
return min;
}
// return number of list elements
int PQueue::PQLength(void) const
{
return count;
}
// test for an empty priority queue
int PQueue::PQEmpty(void) const
{
return count == 0;
}
// test for a full priority queue
int PQueue::PQFull(void) const
{
return count == MaxPQSize;
}
// clear the priority queue by resetting count to 0
void PQueue::ClearPQ(void)
{
count = 0;
}
// 函数名称:Requeue
// 函数作用:将等待在某柜台前面的顾客重新加回队列,重置他们的柜台号和等待时间,并更新总服务时间
// 参数列表:
// ID: int,当前空闲柜台的号码
// time: int, 当前时间
// NT: int,新的柜台号码
// TSTTST: int,总服务时间
// 返回值:int, 更新后的总服务时间
int PQueue::Requeue(int ID, int time, int NT, int TSTTST)
{
// 遍历所有事件对象,找到对应柜台的 departure 事件对象
for (int i = 0; i < count; i++)
{
// 找到对应柜台的 departure 事件对象
if (pqlist[i].GetTellerID() == ID && pqlist[i].GetVIP() == 0 && pqlist[i].GetEventType() == departure)
{
// 重置柜台号和等待时间
pqlist[i].ChangeTheTeller(NT);
pqlist[i].ChangeTheWaittime(time);
// 更新总服务时间
TSTTST -= pqlist[i].GetServiceTime();
}
}
// 返回更新后的总服务时间
return TSTTST;
}
#endif // PRIORITYQUEUE_CLASS
sim.h
#ifndef SIMULATION_CLASS
#define SIMULATION_CLASS
#include <iostream>
#include <iomanip>
#pragma hdrstop
#include "random.h" // include random number generator
using namespace std;
// specifies the two kinds of events
enum EventType { arrival, departure };
class Event {
private:
int time; // 事件发生时间
EventType etype; // 事件类型,到达或离开
int customerID; // 顾客编号
int tellerID; // 服务员编号
int waittime; // 等待时间
int servicetime; // 服务时间
int vipIffo; // 是否为VIP客户的标志
public:
// 构造函数
Event(void);
Event(int t, EventType et, int cn, int tn, int wt, int st, int ifv);
// 获取私有成员的方法
int GetTime(void) const;
EventType GetEventType(void) const;
int GetCustomerID(void) const;
int GetTellerID(void) const;
int GetWaitTime(void) const;
int GetServiceTime(void) const;
int GetVIP(void);
// 修改私有成员数据的方法
void ChangeTheTeller(int newID);
void ChangeTheWaittime(int newwait);
};
// 默认构造函数
Event::Event(void) {}
// 重载构造函数,用于初始化事件信息
Event::Event(int t, EventType et, int cn, int tn, int wt, int st, int ifv) :
time(t), etype(et), customerID(cn), tellerID(tn), waittime(wt), servicetime(st), vipIffo(ifv)
{}
// 返回事件发生时间
int Event::GetTime(void) const {
return time;
}
// 返回事件类型(到达或离开)
EventType Event::GetEventType(void) const {
return etype;
}
// 返回顾客编号
int Event::GetCustomerID(void) const {
return customerID;
}
// 返回服务员编号
int Event::GetTellerID(void) const {
return tellerID;
}
// 返回顾客等待的时间
int Event::GetWaitTime(void) const {
return waittime;
}
// 返回顾客需要服务的时间
int Event::GetServiceTime(void) const {
return servicetime;
}
// 返回是否为VIP客户的标志
int Event::GetVIP(void) {
return vipIffo;
}
// 修改服务员编号的方法
void Event::ChangeTheTeller(int newID) {
tellerID = newID;
}
// 修改等待时间的方法
void Event::ChangeTheWaittime(int newwait) {
waittime = waittime + newwait - time;
}
// 重载小于运算符,用于优先队列中排序
int operator< (Event e1, Event e2) {
return e1.GetTime() < e2.GetTime();
}
// 定义数据类型为事件类型,用于优先队列
typedef Event DataType;
#include "apqueue.h"
// Structure for Teller Info
struct TellerStats
{
int finishService; // when teller available
int totalCustomerCount; // total of customers serviced
int totalCustomerWait; // total customer waiting time
int totalService; // total time servicing customers
int ifonwork;
int vipstarttime;
};
class Simulation
{
private:
int simulationLength; // 模拟的时间长度
int numTellers; // 柜台数
int nextCustomer; // 下一个顾客的 ID
int arrivalLow, arrivalHigh; // 顾客到达时间区间
int serviceLow, serviceHigh; // 服务时间区间
int vipindex; // VIP 顾客在队列中的位置
TellerStats tstat[11]; // 记录每个柜台服务的顾客数量和总服务时间的结构体,最大支持 10 个柜台
PQueue pq; // 普通顾客的优先队列
PQueue vip; // VIP 顾客的优先队列
RandomNumber rnd; // 随机数生成器,用于产生顾客到达和服务的时间
int NextArrivalTime(void); // 计算下一个顾客到达时间
int GetServiceTime(void); // 随机生成当前顾客的服务时间
int NextAvailableTeller(int vip); // 获取下一个空闲的柜台号
int Randomvip(void); // 随机产生一个 VIP 顾客的编号
public:
// 构造函数
Simulation(void);
void RunSimulation(void); // 执行模拟过程
void PrintSimulationResults(void); // 打印模拟结果
};
// constructor initializes simulation data and prompts client
// for simulation parameters
Simulation::Simulation(void)
{
int i;
Event firstevent;
// Initialize Teller Information Parameters
for (i = 1; i <= 10; i++)
{
tstat[i].finishService = 0;
tstat[i].totalService = 0;
tstat[i].totalCustomerWait = 0;
tstat[i].totalCustomerCount = 0;
tstat[i].ifonwork = 0;
tstat[i].vipstarttime = 0;
}
nextCustomer = 1;
vipindex = 0;
// reads client input for the study
cout << "Enter the simulation time in minutes: ";
cin >> simulationLength;
cout << "Enter the number of bank tellers: ";
cin >> numTellers;
cout << "Enter the range of arrival times in minutes: ";
cin >> arrivalLow >> arrivalHigh;
cout << "Enter the range of service times in minutes: ";
cin >> serviceLow >> serviceHigh;
// generate first arrival event
// teller#/waittime/servicetime not used for arrival
pq.PQInsert(Event(0, arrival, 1, 0, 0, 0, 0));
}
// determine random time of next arrival
int Simulation::NextArrivalTime(void)
{
return arrivalLow + rnd.Random(arrivalHigh - arrivalLow + 1);
}
// determine random time for customer service
int Simulation::GetServiceTime(void)
{
return serviceLow + rnd.Random(serviceHigh - serviceLow + 1);
}
// return first available teller
int Simulation::NextAvailableTeller(int vip)
{
// initially assume all tellers finish at closing time
int minfinish = simulationLength;
// assign random teller to customer who arrives
// before closing but obtains service after closing
int minfinishindex = rnd.Random(numTellers) + 1;
if (!vip)
{
// find teller who is free first
for (int i = 1; i <= numTellers; i++)
if (tstat[i].finishService < minfinish)
{
minfinish = tstat[i].finishService;
minfinishindex = i;
}
}
else
{
for (int i = 1; i <= numTellers; i++)
{
if (tstat[i].vipstarttime < minfinish)
{
minfinish = tstat[i].vipstarttime;
minfinishindex = i;
}
}
}
return minfinishindex;
}
int Simulation::Randomvip(void)
{
return rnd.Random(2);
}
///
//***************修改*****************************修改***************************修改***********************//
void Simulation::RunSimulation(void)
{
Event e, newevent; // 定义事件对象e和newevent
int nexttime; // 下一个事件发生的时间
int tellerID; // 处理顾客业务的服务员编号
int servicetime; // 顾客所需服务的时间
int waittime; // 顾客等待的时间
// 进行事件循环,直到优先队列为空
while (!pq.PQEmpty())
{
// 取出优先队列中优先级最高的事件
e = pq.PQDelete();
// 处理到达事件
if (e.GetEventType() == arrival)
{
// 计算下一次到达事件的时间
nexttime = e.GetTime() + NextArrivalTime();
// 判断下一个到达事件是否超出总模拟时间,如果超出则只处理已有事件不再生成新事件
if (nexttime > simulationLength)
continue;
else
{
// 生成下一个顾客的到达事件,并将其加入优先队列
nextCustomer++;
if (Randomvip())
{
vipindex += 1;
newevent = Event(nexttime, arrival,
nextCustomer, 0, 0, 0, vipindex);
// 如果随机出的顾客是VIP,则加入VIP队列中
}
else
newevent = Event(nexttime, arrival,
nextCustomer, 0, 0, 0, 0);
pq.PQInsert(newevent);
}
// 在控制台打印出顾客到达事件的信息:时间、顾客编号、是否为VIP
cout << "Time: " << setw(2) << e.GetTime()
<< " " << "arrival of customer "
<< e.GetCustomerID() << " vip:" << e.GetVIP() << endl;
// 如果当前顾客不是VIP,则生成该顾客的离开事件,将其加入优先队列
servicetime = GetServiceTime();
if (!e.GetVIP())
{
// 找到空闲的服务员,更新服务员的状态
tellerID = NextAvailableTeller(e.GetVIP());
if (tstat[tellerID].finishService <= e.GetTime())
tstat[tellerID].finishService = e.GetTime();
if (tstat[tellerID].finishService - e.GetTime() >= 0)
waittime = tstat[tellerID].finishService -
e.GetTime();
else
waittime = 0;
if (waittime == 0) tstat[tellerID].ifonwork = 1;
tstat[tellerID].totalCustomerWait += waittime; // 更新服务员的统计信息
tstat[tellerID].totalCustomerCount++;
tstat[tellerID].totalService += servicetime;
tstat[tellerID].finishService += servicetime;
if (tstat[tellerID].ifonwork == 1)
tstat[tellerID].vipstarttime = e.GetTime() + servicetime; // 更新VIP的开始服务时间
// 生成该顾客的离开事件,并将其加入优先队列
newevent = Event(tstat[tellerID].finishService,
departure, e.GetCustomerID(), tellerID,
waittime, servicetime, e.GetVIP());
pq.PQInsert(newevent);
}
else // 如果当前顾客是VIP
{
// 找到空闲的服务员,更新服务员的状态
tellerID = NextAvailableTeller(e.GetVIP());
if (tstat[tellerID].finishService <= e.GetTime())
tstat[tellerID].finishService = e.GetTime();
if (tstat[tellerID].vipstarttime - e.GetTime() >= 0)
waittime = tstat[tellerID].vipstarttime -
e.GetTime();
else
{
waittime = 0;
tstat[tellerID].vipstarttime = e.GetTime();
}
tstat[tellerID].totalCustomerWait += waittime; // 更新服务员的统计信息
tstat[tellerID].totalCustomerCount++;
tstat[tellerID].totalService += servicetime;
tstat[tellerID].finishService = tstat[tellerID].vipstarttime + servicetime;
tstat[tellerID].vipstarttime = tstat[tellerID].finishService; // 更新VIP的开始服务时间
// 生成该顾客的离开事件,并将其加入优先队列
newevent = Event(tstat[tellerID].finishService,
departure, e.GetCustomerID(), tellerID,
waittime, servicetime, e.GetVIP());
pq.PQInsert(newevent);
// 如果服务员之前没有在工作状态,则将其加入仍在等待服务的顾客队列
if (tstat[tellerID].ifonwork == 0)
tstat[tellerID].totalService = pq.Requeue(tellerID, tstat[tellerID].finishService, NextAvailableTeller(0), tstat[tellerID].totalService);
tstat[tellerID].ifonwork = 1;
}
}
// 处理离开事件
else
{
tstat[e.GetTellerID()].ifonwork = 0; // 标记该服务员为非工作状态
// 在控制台打印出顾客离开事件的信息:时间、顾客编号、是否为VIP,服务员编号,等待时间和服务时间
cout << "Time: " << setw(2) << e.GetTime()
<< " " << "departure of customer "
<< e.GetCustomerID() << " vip:" << e.GetVIP() << endl;
cout << " Teller " << e.GetTellerID()
<< " Wait " << e.GetWaitTime()
<< " Service " << e.GetServiceTime()
<< endl;
// 找到当前事件对应的服务员的编号
tellerID = e.GetTellerID();
// 如果服务员在该事件发生时已经完成服务,则不需要更新服务员的状态
//if (e.GetTime() == tstat[tellerID].finishService)
//tstat[tellerID].finishService = 0;
}
}
// 考虑服务员加班的情况,更新模拟结束时间
simulationLength = (e.GetTime() <= simulationLength)
? simulationLength : e.GetTime();
}
// summarize the simulation results
void Simulation::PrintSimulationResults(void)
{
int cumCustomers = 0, cumWait = 0, i;
int avgCustWait, tellerWorkPercent;
float tellerWork;
for (i = 1; i <= numTellers; i++)
{
cumCustomers += tstat[i].totalCustomerCount;
cumWait += tstat[i].totalCustomerWait;
}
cout << endl;
cout << "******** Simulation Summary ********" << endl;
cout << "Simulation of " << simulationLength
<< " minutes" << endl;
cout << " No. of Customers: " << cumCustomers << endl;
cout << " Average Customer Wait: ";
avgCustWait = int(float(cumWait) / cumCustomers + 0.5);
cout << avgCustWait << " minutes" << endl;
for (i = 1; i <= numTellers; i++)
{
cout << " Teller #" << i << " % Working: ";
// display percent rounded to nearest integer value
tellerWork = float(tstat[i].totalService) / simulationLength;
tellerWorkPercent = int(tellerWork * 100.0 + 0.5);
cout << tellerWorkPercent << endl;
}
}
#endif // SIMULATION_CLASS
Bank.cpp
#include "sim.h"
int main(void)
{
// declare an object S for our simulation
Simulation S;
// run the simulation
S.RunSimulation();
// print the results
S.PrintSimulationResults();
return 1;
}
/*
<Run #1 of Program 5.7>
Enter the simulation time in minutes: 30
Enter the number of bank tellers: 2
Enter the range of arrival times in minutes: 6 10
Enter the range of service times in minutes: 18 20
Time: 0 arrival of customer 1
Time: 7 arrival of customer 2
Time: 16 arrival of customer 3
Time: 19 departure of customer 1
Teller 1 Wait 0 Service 19
Time: 25 departure of customer 2
Teller 2 Wait 0 Service 18
Time: 37 departure of customer 3
Teller 1 Wait 3 Service 18
******** Simulation Summary ********
Simulation of 37 minutes
No. of Customers: 3
Average Customer Wait: 1 minutes
Teller #1 % Working: 100
Teller #2 % Working: 49
<Run #2 of Program 5.7>
Enter the simulation time in minutes 480
Enter the number of bank tellers 4
Enter the range of arrival times in minutes 2 5
Enter the range of service times in minutes 6 20
<arrival and departure of 137 customers>
******** Simulation Summary ********
Simulation of 521 minutes
No. of Customers: 137
Average Customer Wait: 2 minutes
Teller #1 % Working: 89
Teller #2 % Working: 86
Teller #3 % Working: 83
Teller #4 % Working: 86
*/
仅供参考哦~