问题描述:
已知:
(1)银行有多个窗口
(2)存在不同优先级的客户
要求:
(1)模拟计算客户的到达,服务,离开的过程
(2)模拟计算客户的总逗留事件。
思路:
3种解决方向
1.STL队列与vector实现该问题。
2.C语言数据结构实现链式链表实现该问题。
3.C++面向对象程序设计结合事件驱动编程。
实现:
设计一个队列类(C++prime plus P461)
(1)队列存储有序的项目序列
(2)能够创建空队列
(3)队列空间有一定限制
(3)能够检查队列是否为空
(4)能够检查队列是否已满
(5)能够在队尾添加元素(push操作)
(6)能够在队首删除元素(pop操作)
(7)能够确定队列中的项目数
基础队列模拟部分:
#include
#include
#include
using namespace std;
int main()
{
queue<int>Customer[3];//两类客户
vector<int>num(3,0);//窗口编号,不妨假设存在2个窗口
int T;//客户个数
cin>>T;
int n = T;
while(T--)
{
char s;
cin>>s;
if(s == 'V')//VIP客户
{
Customer[0].push(1);
num[1]++;
}
else if(s == 'N')//normal普通客户
{
Customer[0].push(2);
num[2]++;
}
}
while(n--)
{
int _time;
cin>>_time;//输入时间
Customer[Customer[0].front()].push(_time);
Customer[0].pop();
}
vector<int>sum(4,0);//求出每类客户的平均时间。
while(!Customer[1].empty())
{
sum[1]+= Customer[1].front();
Customer[1].pop();
}
while(!Customer[2].empty())
{
sum[2]+= Customer[2].front();
Customer[2].pop();
}
cout<<"VIP客户的平均办理时间:"<<sum[1]/num[1]<<"\n"<<"NORMAL客户的平均办理时间:"<<sum[2]/num[2]<<endl;
}
问题:上述算法仅仅求出了每种客户服务时间的平均值,但并没有考虑排队问题,也就是排队时间 + 服务时间 = 逗留事件。既然要模拟排队过程,那就必须确定某一时间某个人选择了哪个窗口,实现这个过程要么有固定的模式,要么就用随机数模拟,下面的一个代码就通过vector矢量类实现了随机过程模拟(power by yz)。
#include
#include
#include
#include
#include
#include
using namespace std;
const int LAST_TIME = 1000; //持续时间
const int TIME_MAX = 5; //每个人排队的最大时间
class Customers
{
int time_use;
int time_arrive;
int mode;
bool type; //顾客类型,如果type = true代表VIP用户
int num; //用户编号
static int id;
public:
Customers(int time_use = 0,int time_arrive = 0,int mode = -1,bool type = false):time_use(time_use),time_arrive(time_arrive),mode(mode),type(type)
{
num = id++;
}
~Customers(){}
int getTime_use() const
{
return time_use;
}
// 获取到达的时间
int getTime_arrive() const
{
return time_arrive;
}
// 返回该顾客所在的位置,0-4代表五个窗口,-1代表在队列中
int getMode() const
{
return mode;
}
bool is_VIP() const
{
return type;
}
int getNum() const
{
return num;
}
int static getID()
{
return id;
}
};
int Customers::id = 1000; //人员编号
struct Windows
{
int number; //窗口编号
bool work; //判断是否在处理
Customers customer; //当前窗口的顾客
}windows[5] = { {0,false},{1,false}, {2,false},{3,false},{4,false} };//代表四个窗口
int go();
int main()
{
int sum = go();
printf("\n一共服务了%d个人,每个窗口平均处理%d个人\n", Customers().getID()- 1000, (Customers().getID() -1000) / 5);
printf("人均办理时间: %3d \n", sum / LAST_TIME);
return 0;
}
//mode = true代表VIP用户,mode == false代表普通用户
void Customer_insert(bool mode, int time, vector<Customers>v)
{
if (!mode) //普通用户
{
for (int i = 0; i < 5; i++)
{
if (!windows[i].work) //窗口空闲
{
windows[i].customer = Customers(1 + rand() % TIME_MAX, time, i); //普通用户直接进入窗口
windows[i].work = true;
printf("当前时间:%5d : 编号为%6d的普通顾客进入窗口%d办理业务!\n\n",time, windows[i].customer.getNum(), i);
return;
}
}
v.push_back({ 1 + rand() % TIME_MAX,time,-1 }); //窗口均处于办理状态,则刚来的人需要排队,并在队尾
printf("当前时间:%5d : 编号为%6d的普通顾客前往排队\n\n",time, v.back().getNum());
}
else //VIP用户
{
for (int i = 0; i < 5; i++)
{
if (!windows[i].work) //窗口空闲
{
windows[i].customer = Customers(1 + rand() % TIME_MAX, time, i, true); //VIP用户直接进入窗口
windows[i].work = true;
printf("当前时间:%5d : 编号为%6d的VIP顾客进入窗口%d办理业务!\n\n", time,windows[i].customer.getNum(), i);
return;
}
}
for (int i = 0; i < v.size();i++)
{
if (v[i].is_VIP())
{
continue;
}
else
{
v.insert(v.begin() + i, { 1 + rand() % TIME_MAX, time, -1, true });
printf("当前时间:%5d : 编号为%6d的VIP顾客前往排队\n\n",time, v.back().getNum());
return;
}
}
v.push_back({ 1 + rand() % TIME_MAX, time,-1, true });
printf("当前时间:%5d : 编号为%6d的VIP顾客前往排队\n\n",time, v.back().getNum());
}
}
int go()
{
vector<Customers>v; //保存排队中的人
int now = 0;
srand((unsigned)time(NULL));
int sum = 0;
while (1)
{
now++;
if (now >= LAST_TIME)
{
for (int i = 0; i < 5;i++)
{
if (windows[i].work)
//先判断是否空闲,然后判断客户是否处理完毕业务
{
if(windows[i].customer.is_VIP())
printf("当前时间:%5d : 编号为%6d的VIP顾客离开了%d窗口\n\n", now, windows[i].customer.getNum(), i); //输出信息
else
printf("当前时间:%5d : 编号为%6d的普通顾客离开了%d窗口\n\n", now, windows[i].customer.getNum(), i); //输出信息
windows[i].work = false; //空闲状态
sum += 1000 - windows[i].customer.getTime_arrive();
}
}
return sum;
}
for (int i = 0; i < 5;i++)
{
if (windows[i].work && windows[i].customer.getTime_arrive() + windows[i].customer.getTime_use() <= now)
//先判断是否空闲,然后判断客户是否处理完毕业务
{
//cout <<"get" <
if (windows[i].customer.is_VIP())
printf("当前时间:%5d : 编号为%6d的VIP顾客离开了%d窗口\n\n", now, windows[i].customer.getNum(), i); //输出信息
else
printf("当前时间:%5d : 编号为%6d的普通顾客离开了%d窗口\n\n", now, windows[i].customer.getNum(), i); //输出信息
windows[i].work = false; //空闲状态
sum +=windows[i].customer.getTime_use();
}
}
int rand_number = 1 + rand() % 10;
if (rand_number <= 3)continue; //3/10的可能性没有人来排队
else if (rand_number <= 9) //6/10的可能性为普通用户
{
Customer_insert(false, now, v);
}
else //VIP用户
{
Customer_insert(true, now, v);
}
}
}
上述代码通过矢量类方便插入的功能实现了排队和插队的功能,同时利用随机数生成模拟每个人排队和办理的过程,中间还建立了Customer类,基本实现了类的结构。
不过既然学习的是C++和队列模拟,向量的使用再方便,也是不能完全达到学习目的的,接下来的代码分别用纯C和面向对象的方法实现了队列的抽象数据类型。
//离散事件模拟,模拟银行营业时的排队情况
//不考虑顾客中途离开,顾客到达事件随机,业务办理时间
//长度随机,选择最短的队排队,不再换队
//作者:nuaazdh
//时间:2011年12月10日 08:52:37
#include
#include
#include
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
typedef struct Event{ //事件类型
int OccurTime; //事件发生时刻
int NType; //事件类型,0表示到达事件,1至4表示四个窗口的离开事件
struct Event *next;
}Event,ElemType;
typedef struct{ //单向链表结构
ElemType *head;//头指针
ElemType *tail;//尾指针
int len; //长度
}LinkList;
typedef LinkList EventList; //事件链表
typedef struct QElemType{ //队列元素
int ArriveTime;//到达时间
int Duration;//办理业务所需时间
struct QElemType *next;
}QElemType;
typedef struct{//队列结构
QElemType *head;//头指针
QElemType *tail;//尾指针
}LinkQueue;
Event NewEvent(int occurT,int nType);
//根据OccurTime和NType值,创建新事件
Status InitList(LinkList *L);
//初始化事件链表
Status OrderInsert(LinkList *L,Event e);
//将事件e按发生时间顺序插入有序链表L中
Status ListEmpty(LinkList *L);
//判断链表L是否为空,为空返回TRUE,否则返回FALSE
Status DelFirst(LinkList *L,ElemType *e);
//链表L不为空,删除其首结点,用e返回,并返回OK;否则返回ERROR
Status ListTraverse(LinkList *L);
//遍历链表
Status InitQueue(LinkQueue *Q);
//初始化队列Q
Status EmptyQueue(LinkQueue *Q);
//若队列Q为空,返回TRUE,否则返回FALSE
Status DelQueue(LinkQueue *Q,QElemType *e);
//若队列Q不为空,首结点出队,用e返回,并返回OK;否则返回ERROR
Status EnQueue(LinkQueue *Q,QElemType e);
//结点e入队Q
int QueueLength(LinkQueue Q);
//返回队列Q的长度,即元素个数
Status GetHead(LinkQueue *Q,QElemType *e);
//若队列Q不为空,用e返回其首结点,并返回OK,否则返回ERROR
Status QueueTraverse(LinkQueue *Q);
//遍历队列Q
//------------------//
int Min(int a[],int n);
//返回长度为n的数组a第一个最小值的下标,从1开始
int ShortestQueue();
//获取最短队列的编号
void OpenForDay();
//初始化操作
void CustomerArrived();
//顾客达到事件
void CustomerDepature();
//顾客离开事件
void Bank_Simulation();
//银行排队模拟
void PrintEventList();
//输出事件队列
void PrintQueue();
//打印当前队列
//----全局变量-----//
EventList ev;
Event en;
LinkQueue q[5];
QElemType customer;
int TotalTime,CustomerNum;
int CloseTime=200;//关闭时间,即营业时间长度
//--------------main()------------------//
int main()
{
Bank_Simulation();
return 0;
}
//--------------模拟排队----------------//
void OpenForDay(){
//初始化操作
int i;
TotalTime=0; CustomerNum=0;
InitList(&ev);//初始化事件队列
en.OccurTime=0;
en.NType=0;
OrderInsert(&ev,en);
for(i=1;i<=4;i++)
InitQueue(&q[i]);//初始化四个窗口队列
}//OpenForDay
void CustomerArrived(){
//顾客达到事件
int durtime,intertime,i,t;
QElemType e;
++CustomerNum;
intertime=rand()%5+1;//间隔时间在5分钟内
durtime=rand()%30+1;//办理业务时间在30分钟内
t=en.OccurTime+intertime;
if( en.OccurTime<CloseTime){//银行尚未关门
printf("A new customer arrived at:%d,his durTime=%d,the next intertime=%d|\n",en.OccurTime,durtime,intertime);//下一位顾客达到时间
OrderInsert(&ev,NewEvent(t,0));
i=ShortestQueue();//最短队列
e.ArriveTime=en.OccurTime;
e.Duration=durtime;
EnQueue(&q[i],e);
if(QueueLength(q[i])==1)
OrderInsert(&ev,NewEvent(en.OccurTime+durtime,i));
}else{
printf("maxinum exceed!stop,en.OccurTime=%d,intertime=%d\n",en.OccurTime,intertime);
}
}
void CustomerDepature(){
//顾客离开事件
int i=en.NType;
DelQueue(&q[i],&customer);
printf("A customer leaves at:%d\n",en.OccurTime);//输出顾客离开时间
TotalTime+=en.OccurTime-customer.ArriveTime;
if(!EmptyQueue(&q[i])){
GetHead(&q[i],&customer);
OrderInsert(&ev,NewEvent(en.OccurTime+customer.Duration,i));
}
}
void Bank_Simulation(){
//银行排队模拟
OpenForDay();
srand((unsigned)time(NULL));
while(!ListEmpty(&ev)){
DelFirst(&ev,&en);
printf("--------action--------------------------\n");
if(en.NType==0)
CustomerArrived();
else
CustomerDepature();
PrintQueue();
PrintEventList();
}
printf("\nTotal time is: %d min,average time is: %g min.\n",TotalTime,(float)TotalTime/CustomerNum);
}
void PrintQueue(){
//打印当前队列
int i;
for(i=1;i<=4;i++){
printf("Queue %d have %d customer(s):",i,QueueLength(q[i]));
QueueTraverse(&q[i]);
}
printf("\n");
}
void PrintEventList(){
//输出事件队列
printf("Current Eventlist is:\n");
ListTraverse(&ev);
}
int Min(int a[],int n){
//返回长度为n的数组a第一个最小值的下标,从0开始
int i,tmp,ind=0;
tmp=a[0];
for(i=1;i<n;i++){
if(a[i]<tmp){
tmp=a[i];
ind=i;
}
}
return ind;
}
int ShortestQueue(){
//获取最短队列的编号
int i,a[4];
for(i=1;i<=4;i++){
a[i-1]=QueueLength(q[i]);
//printf("队%d的长度为%d\n",i,QueueLength(q[i]));
}
return Min(a,4)+1;//队列从1开始编号
}
//-----------队和链表操作--------------//
Event NewEvent(int occurT,int nType){
//根据OccurTime和NType值,创建新事件
Event e;
e.OccurTime=occurT;
e.NType=nType;
return e;
}
Status InitList(LinkList *L){
//初始化事件链表
L->head=L->tail=(ElemType *)malloc(sizeof(ElemType));
if(!L->head){
printf("Apply for memory error.LinkList initialize failed.\n");
exit(0);
}
L->head->next=NULL;
return OK;
}
Status OrderInsert(LinkList *L,Event e){
//将事件e按发生时间顺序插入有序链表L中
ElemType *p,*q,*newptr;
newptr=(ElemType *)malloc(sizeof(ElemType));
if(!newptr){
printf("Apply for memory error,new node can't insert intot the Eventlist.\n");
exit(0);
}
*newptr=e;
if(TRUE==ListEmpty(L)){//链表为空
L->head->next=newptr;
L->tail=newptr;
L->tail->next=NULL;
return OK;
}
q=L->head;
p=L->head->next;
while(p){//遍历整个链表
if(p->OccurTime>=newptr->OccurTime)
break;
q=p;
p=p->next;
}
q->next=newptr;
newptr->next=p;
if(!p)//插入位置为链表尾部
L->tail=newptr;
return OK;
}
Status ListEmpty(LinkList *L){
//判断链表L是否为空,为空返回TRUE,否则返回FALSE
if((L->head==L->tail)&&(L->head!=NULL))
return TRUE;
else
return FALSE;
}
Status DelFirst(LinkList *L,ElemType *e){
//链表L不为空,删除其首结点,用e返回,并返回OK;否则返回ERROR
ElemType *p=L->head->next;
if(!p)
return ERROR;
L->head->next=p->next;
*e=*p;
free(p);
if(L->head->next==NULL)
L->tail=L->head;
return OK;
}
Status ListTraverse(LinkList *L){
//遍历链表
Event *p=L->head->next;
if(!p){
printf("List is empty.\n");
return ERROR;
}
while(p!=NULL){
printf("OccurTime:%d,Event Type:%d\n",p->OccurTime,p->NType);
p=p->next;
}
printf("\n");
return OK;
}
Status InitQueue(LinkQueue *Q){
//初始化队列Q
Q->head=Q->tail=(QElemType *)malloc(sizeof(QElemType));
if(!Q->head){
printf("Apply for memory error.LinkQueue initialize failed.\n");
exit(0);
}
Q->head->next=NULL;
return OK;
}
Status EmptyQueue(LinkQueue *Q){
//若队列Q为空,返回TRUE,否则返回FALSE
if(Q->head==Q->tail&&Q->head!=NULL)
return TRUE;
else
return FALSE;
}
Status DelQueue(LinkQueue *Q,QElemType *e){
//若队列Q不为空,首结点出队,用e返回,并返回OK;否则返回ERROR
QElemType *p=Q->head->next;
if(!p)
return ERROR;
*e=*p;
Q->head->next=p->next;//修正队首指针
free(p);
if(!Q->head->next)//队空
Q->tail=Q->head;
return OK;
}
Status EnQueue(LinkQueue *Q,QElemType e){
//结点e入队Q
QElemType *p=(QElemType *)malloc(sizeof(QElemType));
if(!p){
printf("Apply for memory error,new element can't enqueue.\n");
exit(0);
}
*p=e;
p->next=NULL;
Q->tail->next=p;//插入队尾
Q->tail=p;//修改队尾指针
return OK;
}
int QueueLength(LinkQueue Q){
//返回队列Q的长度,即元素个数
int count=0;
QElemType *p=Q.head->next;
while(p){
p=p->next;
count++;
}
return count;
}
Status GetHead(LinkQueue *Q,QElemType *e){
//若队列Q不为空,用e返回其首结点,并返回OK,否则返回ERROR
if(EmptyQueue(Q))
return ERROR;
*e=*(Q->head->next);
return OK;
}
Status QueueTraverse(LinkQueue *Q){
//遍历队列Q
QElemType *p=Q->head->next;
if(!p){
printf("--Is empty.\n");
return ERROR;
}
while(p){
printf("(%d,%d) ",p->ArriveTime,p->Duration);
p=p->next;
}
printf("\n");
return OK;
}
先暂时贴个代码,以后填坑吧。。。
#include
#include
#include
#include
#include
#define RANDOM_PARAMETER 100//生成随机数的区间0-99
using namespace std;
//大大的疑问????把函数放在类里???
class Random {//随机数生成类
public:
// [0, 1) 之间的服从均匀分布的随机值???
static double uniform(double max = 1) {
return ((double)std::rand() / (RAND_MAX))*max;
}
};
typedef struct costomer{
//顾客的数据结构
//顾客是队列的数据存储基础,所以操作上没要求,用结构体就ok
int arrive_time;//顾客的随机到达时间
int duration;//顾客业务的随机耗费时间
costomer * next;
// 结构体的默认构造函数???
costomer(int arrive_time = 0,int duration = Random::uniform(RANDOM_PARAMETER)) :arrive_time(arrive_time),
duration(duration) ,next(nullptr){}
//在结构体的构造函数中,实现对duration的随机数的生成
} Costomer;
//窗口状态的枚举
enum Win_Status {
SERVICE,//服务中0
IDLE//空闲1
};
//工作窗口类定义
class ServiceWindows {
private:
Costomer costomer;//存储处理客户的信息
Win_Status status;//表示窗口状态
public:
ServiceWindows()//构造函数
{
status = IDLE;//初始的时候空闲
}
void setBusy()//窗口设置为繁忙
{
status = SERVICE;
}
void setIdle()//窗口设置为空闲
{
status = IDLE;
}
inline void serveCustomer(Costomer &customer) {//读取新客户业务
costomer = customer;
}
bool IsIdle()
{
if (status == IDLE)
return true;
else
return false;
}
int getArriveTime()
{
return costomer.arrive_time;
}
int getDurationTime()
{
return costomer.duration;
}
};
//设计事件表,即,事件的数据结构
struct Event {
int occur_time;//事件发生的时间,用于之后的事件的排序
//描述时间的类型,-1表示到达,》=0表示离开,并且表示相应的窗口编号
int EventType;
Event * next;
//所以,又是结构体的构造函数?
Event(int time = Random::uniform(RANDOM_PARAMETER) ,int type = -1):occur_time(time),EventType(type)
,next(nullptr) {}
};
//可插入队列的的实现
template<class T>
class Queue {
private:
T * front;
T * rear;//头指针and尾指针
public:
Queue();//构造函数,带有头节点的
~Queue();//析构函数
void clearQueue();//清空队列
T* enqueue(T & join);//入队
T * dequeue();//出队
T * orderEnqueue(Event& event);//只适用于事件入队
int length();//获得队列长度
};
//系统队列的设计
class QueueSystem {
private:
int total_service_time;//总的服务时间
int total_costomer;//总的服务顾客总数
int total_stay_time;//总的等待时间
int windows_number;//窗口数目
int avg_stay_time;//平均时间
int avg_costomers;//平均顾客数目
ServiceWindows* windows;//创建服务窗口数组的指针
Queue<Costomer> customer_list;//客户排队等待的队列
Queue<Event> event_list;//时间队列????
Event* current_event;//事件指针
double run();// 让队列系统运行一次
void init();// 初始化各种参数
void end();// 清空各种参数
int getIdleServiceWindow();// 获得空闲窗口索引
void customerArrived();// 处理顾客到达事件
void customerDeparture();// 处理顾客离开事件
public:
// 初始化队列系统,构造函数
QueueSystem(int total_service_time, int window_num);
// 销毁,析构函数
~QueueSystem();
// 启动模拟,
void simulate(int simulate_num);
inline double getAvgStayTime() {
return avg_stay_time;
}
inline double getAvgCostomers() {
return avg_costomers;
}
};
int main()
{
srand((unsigned)std::time(0)); // 使用当前时间作为随机数种子
int total_service_time = 240; // 按分钟计算
int window_num = 4;
int simulate_num = 100000; // 模拟次数????这是干嘛用的???
QueueSystem system(total_service_time, window_num);//构建这个系统,初始化
system.simulate(simulate_num);//开启模拟???这又是神马意思
cout << "The average time of customer stay in bank: "
<< system.getAvgStayTime() << endl;
cout << "The number of customer arrive bank per minute: "
<< system.getAvgCostomers() << endl;
getchar();
return 0;
}
template<class T>
Queue<T>::Queue()
{
front = new T;//有一个头节点的链表
if (!front)
exit(1);//内存分配失败,终止程序
rear = front;
front->next = nullptr;//头节点
}
template<class T>
Queue<T>::~Queue()//析构函数,清空链表,释放头节点
{
clearQueue();
delete front;//释放头节点内存
}
template<class T>
void Queue<T>::clearQueue()
{
T *temp_node;
//清空链表的时候用头节点往前边推进,知道最后的NULL,这个方法比较巧妙
while (front->next) {
temp_node = front->next;
front->next = temp_node->next;
delete temp_node;
}
this->front->next = NULL;
this->rear = this->front;
}
template<class T>
T * Queue<T>::enqueue(T & join)
{//从队尾加入
T * new_node= new T;
if (!new_node)
exit(1);
*new_node = join;
new_node->next = nullptr;
rear->next = new_node;
rear = rear->next;
return front;//返回头指针,
}
template<class T>
T * Queue<T>::dequeue()//注意,这里实现的不是删除节点,而是将节点从链表拆除,拿走使用
{
if (!front->next)//空,全面的错误检查
return nullptr;
T * temp = front->next;
front->next = temp->next;//将首节点拆除,以便于后来带走
if (!front->next)//错误预警,判断是不是拿走的是不是最后一个元素
rear = front;
return temp;//返回出队的元素指针,在这里不释放。
}
template<class T>
int Queue<T>::length()
{
T *temp_node;
temp_node = this->front->next;
int length = 0;
while (temp_node) {
temp_node = temp_node->next;
++length;
}
return length;
}
template<class T>
T * Queue<T>::orderEnqueue(Event & event)//对于事件列表,要按照时间的顺序插入
{
Event* temp = new Event;
if (!temp) {
exit(-1);
}
*temp = event;//赋值
// 如果这个列表里没有事件, 则把 temp 事件插入
if (!front->next) {
enqueue(*temp);
delete temp;
return front;
}
// 按时间顺序插入
Event *temp_event_list = front;
// 如果有下一个事件,且下一个事件的发生时间小于要插入的时间的时间,则继续将指针后移
while ( temp_event_list->next && temp_event_list->next->occur_time < event.occur_time) {
temp_event_list = temp_event_list->next;
}//最终得到的temp_event_list的下一个是时间大于新输入event的,所以应该插入在temp_event_list之后
// 将事件插入到队列中
temp->next = temp_event_list->next;
temp_event_list->next = temp;
// 返回队列头指针
return front;
}
/*
我们来看入队方法和出队方法中两个很关键的设计:
入队时尽管引用了外部的数据,但是并没有直接使用这个数据,反而是在内部新分配了一块内存,再将外部数据复制了一份。
出队时,直接将分配的节点的指针返回了出去,而不是拷贝一份再返回。
在内存管理中,本项目的代码使用这样一个理念:谁申请,谁释放。
队列这个对象,应该管理的是自身内部使用的内存,释放在这个队列生命周期结束后,依然没有释放的内存。
*/
QueueSystem::QueueSystem(int total_service_time, int window_num):
total_service_time(total_service_time),
windows_number(window_num),
total_stay_time(0),
total_costomer(0)
{//构造函数
windows = new ServiceWindows[windows_number];//创建 num 个工作窗口
}
QueueSystem::~QueueSystem()
{
delete [] windows ;//释放窗口内存
}
void QueueSystem::simulate(int simulate_num)//这个地方一直没搞懂,模拟?
{
double sum = 0;//累计模拟次数????
//这个循环可以说是这个系统跑起来运行的发动机吧
for (int i = 0; i != simulate_num; ++i) {
// 每一遍运行,我们都要增加在这一次模拟中,顾客逗留了多久
sum += run();
}
/*模拟结束,进行计算,类似复盘*/
// 计算平均逗留时间
avg_stay_time = (double)sum / simulate_num;
// 计算每分钟平均顾客数
avg_costomers = (double)total_costomer / (total_service_time*simulate_num);
}
// 系统开启运行前, 初始化事件链表,第一个时间一定是到达事件,所以采用默认构造就ok
void QueueSystem::init() {
Event *event = new Event;//创建一个默认的事件,到达。
current_event = event;//并且是当前事件
}
// 系统开始运行,不断消耗事件表,当消耗完成时结束运行
double QueueSystem::run() {
init();//在这里初始化????
while (current_event) {
// 判断当前事件类型
if (current_event->EventType == -1) {
customerArrived();//事件类型为-1,处理客户到达事件
}
else {
customerDeparture();//处理客户离开事件
}
delete current_event;//处理完毕,释放当前的事件
// 从事件表中读取新的事件
current_event = event_list.dequeue();//出队列,
};
end();//结束
// 返回顾客的平均逗留时间
return (double)total_stay_time / total_costomer;
}
// 系统运行结束,将所有服务窗口置空闲。并清空用户的等待队列和事件列表????
void QueueSystem::end() {
// 设置所有窗口空闲
for (int i = 0; i != windows_number; ++i) {
windows[i].setIdle();
}
// 顾客队列清空
customer_list.clearQueue();
// 事件列表清空
event_list.clearQueue();
}
// 处理用户到达事件
void QueueSystem::customerArrived() {
total_costomer++;//用户数目++
// 生成下一个顾客的到达事件
int intertime = Random::uniform(100); // 下一个顾客到达的时间间隔,我们假设100分钟内一定会出现一个顾客
// 下一个顾客的到达时间 = 当前时间的发生时间 + 下一个顾客到达的时间间隔
int time = current_event->occur_time + intertime;
Event temp_event(time);//结构体构造函数,参数为到达时间,然后业务时间在构造函数中生成
// 如果下一个顾客的到达时间小于服务的总时间,就把这个事件插入到事件列表中
if (time < total_service_time) {
event_list.orderEnqueue(temp_event);
} // 否则不列入事件表,且不加入 cusomer_list
// 同时将这个顾客加入到 customer_list 进行排队
// 处理当前事件中到达的顾客
Costomer *customer = new Costomer(current_event->occur_time);
if (!customer) {
exit(-1);
}
customer_list.enqueue(*customer);//将的用户加入列表
// 如果当前窗口有空闲窗口,那么直接将队首顾客送入服务窗口
int idleIndex = getIdleServiceWindow();
if (idleIndex >= 0) {
customer = customer_list.dequeue();//客户指针
windows[idleIndex].serveCustomer(*customer);//将客户信息传递给空闲的窗口处理
windows[idleIndex].setBusy();//窗口设置为忙碌
// 顾客到窗口开始服务时,就需要插入这个顾客的一个离开事件到 event_list 中
// 离开事件的发生时间 = 当前时间事件的发生时间 + 服务时间
Event temp_event(current_event->occur_time + customer->duration, idleIndex);
event_list.orderEnqueue(temp_event);//将离开的事件按照时间的先后插入事件链表
}
delete customer;//释放已经传递到窗口的客户信息
}
//获取空闲窗口的序号
int QueueSystem::getIdleServiceWindow() {
for (int i = 0; i != windows_number; ++i) {//遍历查找
if (windows[i].IsIdle()) {
return i;
}
}
return -1;
}
// 处理用户离开事件
void QueueSystem::customerDeparture() {
// 如果离开事件的发生时间比总服务时间大,我们就不需要做任何处理
if (current_event->occur_time < total_service_time) {
// 顾客总的逗留时间 = 当前顾客离开时间 - 顾客的到达时间
total_stay_time += current_event->occur_time - windows[current_event->EventType].getArriveTime();
// 如果队列中有人等待,则立即服务等待的顾客
//把窗口交给排队中的新的客户
if (customer_list.length()) {
Costomer *customer;
customer = customer_list.dequeue();
windows[current_event->EventType].serveCustomer(*customer);
// 因为有新的客户进入柜台,所以要为这个新的客户编写离开事件事件,并送到事件列表中
Event temp_event(
current_event->occur_time + customer->duration,
current_event->EventType
);
event_list.orderEnqueue(temp_event);
delete customer;
}
else {
// 如果队列没有人,且当前窗口的顾客离开了,则这个窗口是空闲的
windows[current_event->EventType].setIdle();
}
}
}
https://blog.csdn.net/weixin_35929051/article/details/52494728
https://blog.csdn.net/taikeqi/article/details/76652229
https://blog.csdn.net/weixin_41879093/article/details/82843772
https://www.shiyanlou.com/courses/557