这里我们模拟一下银行排队叫号系统的实现:
假设一个银行有4个窗口对外接待客户。由于每个窗口在某一时刻只能接待一个客户,在客户众多的时候需要排队,对于刚进入银行的客户,如果某个窗口正空闲,
则可上前办理业务,如果所有窗口都不空闲则排在人数最少的窗口。
现在要求模拟银行的某一时间段内的4个窗口的客户排队情况。这里客户到达的时刻和办理业务的时间都是随机的。
首先我们银行发生事件,我们得有一个类表示事件对象
/*
*功能:这个实现的是我们事件的数据单元节点
*文件:Event.h
*时间:2015年7月8日20:55:32
*作者:cutter_point
*/
#ifndef EVENT_H
#define EVENT_H
class Event
{
int OccurTime; //事件发生的时刻
int NType; //事件类型,0表示到达事件,1到4表示四个窗口的离开事件
public:
Event() //初始化,我们把事件发生的事件和事件类型都首先视为0
{
this->OccurTime = 0;
this->NType = 0;
}
Event(int Occur, int type)
{
this->OccurTime = Occur;
this->NType = type;
}
//重写=操作符,运算符的重载
/*
Event& operator=(const Event& event)
{
if (event == NULL)
{
}
}
*/
//set和get方法
void setOccurTime(int OccurTime) { this->OccurTime = OccurTime; }
int getOccurTime() { return this->OccurTime; }
void setNType(int type) { this->NType = type; }
int getNType() { return this->NType; }
};
#endif/*
*功能:这个实现的是链表的功能
*文件:LinkList.h
*时间:2015年7月7日20:33:18,2015年7月8日20:01:31
*作者:cutter_point
*/
#ifndef LINKLIST_H
#define LINKLIST_H
#include "Event.h"
#include <stdio.h>
using namespace std;
//链表的一个节点
class Node
{
Event event; //创建我们的数据元对象,包含一个事件发生时间和一个事件类型
Node* next;
public:
Node() { this->next = nullptr; }
Node(Event e) { this->event = e; this->next = nullptr; }
//set和get方法
void setEvent(Event e) { this->event = e; }
Event getEvent() { return this->event; }
void setNext(Node* n) { this->next = n; }
Node* getNext() { return this->next; }
};
//我们的链表
class LinkList
{
Node* head; //头指针
Node* getTail(); //得到尾部指针
public:
LinkList(){ this->head = new Node(); }
void insert(Event e); //给链表末尾添加一个元素
Node* pop(); //删除最后一个节点
bool listEmpty(); //判断是否是空链表
Node* getHead(); //取得头指针
};
#endif
/*
*功能:这个实现的是链表的功能
*文件:LinkList.cpp
*时间:2015年7月7日20:33:18,2015年7月8日20:01:31,2015年7月9日20:42:11
*作者:cutter_point
*/
#include "LinkList.h"
#include <iostream>
using namespace std;
/*******************************************************Node****************************************************************************/
/*******************************************************LinkList****************************************************************************/
//void insert(Event e); //给链表末尾添加一个元素
void LinkList::insert(Event e)
{
//插入一个元素,首先我们判断链表中是否含有元素
bool a;
if (a = this->listEmpty()) //为true就是真
this->head = new Node();
//现在不管是否含有元素都有一个元素了,但是这个元素时空的,来作为头,接着我们把新的元素添加进去
Node *p = this->head; //用一个游标指针
Node* val = new Node(e);
//然后把新的val接到元素的末尾
p = this->getTail();
//现在p指向尾元素,我们把元素接到末尾
p->setNext(val); //吧val设为尾部的元素
}
//Node* getHead(); //取得头指针
Node* LinkList::getHead()
{
return this->head;
}
Node* LinkList::getTail()
{
Node* p = this->head;
while (p->getNext() != nullptr)
{
p = p->getNext(); //循环到最后一个节点
}
return p;
}
//Node* pop(); //删除最后一个节点
Node* LinkList::pop()
{
//用一个游标指针
Node* p = this->getHead();
Node* q = this->getHead();
//首先判断是否还有元素可以删除,这个的判断条件就是头指针后面那个是不是为空的
if (p->getNext() == nullptr)
{
cout << "事件表为空" << endl;
return nullptr;
}
//循环到最后一个和倒数第二个
while (p->getNext() != nullptr)
{
q = p; //得到上一个节点
p = p->getNext(); //去下一个节点
}
//如果末尾不为空,说明还有元素,那么我们就删除掉最后一个,并返回最后一个
q->setNext(nullptr); //吧倒数第二个设定为空,断开连接
return p; //返回断开后的那个数据的指针
}
//bool listEmpty(); //判断是否是空链表
bool LinkList::listEmpty()
{
//判断是否为空的标识就是头指针和尾指针是同一个节点,那么就代表为空
if (this->getHead() == this->getTail())
{
//没有下一个节点
return true;
}
return false; //头结点的下一个不为空
}
然后我们用一个队列对象,来表示银行窗口的队列:
/*
*功能:这个实现的是我们事件的队列
*文件:LinkQueue.h
*时间:2015年7月9日17:17:09
*作者:cutter_point
*/
#ifndef LINKQUEUE_H
#define LINKQUEUE_H
class QNode
{
int arrivalTime; //时间到达时间
int duration; //处理业务所需要的时间
QNode* next; //指向下一个节点
public:
QNode()
{
this->arrivalTime = 0;
this->duration = 0;
this->next = nullptr;
}
QNode(QNode* e)
{
this->arrivalTime = e->arrivalTime;
this->duration = e->duration;
this->next = nullptr;
}
//set和get方法
void setArrivalTime(int arrival) { this->arrivalTime = arrival; }
int getArrivalTime() { return this->arrivalTime; }
void setDuration(int dur) { this->duration = dur; }
int getDuration() { return this->duration; }
void setNext(QNode* n) { this->next = n; }
QNode* getNext() { return this->next; }
};
class LinkQueue
{
//QNode* base; //初始化的动态分配存储空间
QNode* front; //队列的头
QNode* rear; //队列的尾
public:
LinkQueue(); //初始化
void enQueue(QNode* e); //插入数据
QNode deQueue(); //删除一个数据
bool queueEmpty(); //判断队列是否为空
QNode getHead(); //获取队列的头元素
int length();
//set和get方法
void setFront(QNode* qn) { this->front = qn; }
QNode* getFront() { return this->front; }
void setRear(QNode* qn) { this->rear = qn; }
QNode* getRear() { return this->rear; }
};
#endif
/*
*功能:这个实现的是我们事件的队列
*文件:LinkQueue.h
*时间:2015年7月9日17:17:09,2015年7月10日15:18:58
*作者:cutter_point
*/
#include "LinkQueue.h"
//LinkQueue(); //初始化
LinkQueue::LinkQueue()
{
//初始化队列的时候,队列是先进先出
//首先我们创建一个新的空节点
this->rear = this->front = new QNode();
this->front->setNext(nullptr); //初始化的时候只有一个节点,后面的设置为nullptr
}
//得到队列的长度
int LinkQueue::length()
{
//由于第一个是空的,所以我们判断队列的长度就是尾部和首部的长度
int length = 0;
QNode* p = this->front; //用一个游标当做指针,指向相应的位置
while (p != this->rear)
{
p = p->getNext();
++length;
}
return length; //返回队列的长度
}
//void enQueue(QNode* e); //插入数据
void LinkQueue::enQueue(QNode* e)
{
//吧原始e插入到队列中,由于我们的是链表,不用考虑队列会不会满的问题,如果是线性表,我们还要考虑队列是否满的问题,因为初始化的时候,线性表示一次性申请完全的
this->rear->setNext(e);
//然后重置尾节点
this->rear = this->rear->getNext();
}
//QNode deQueue(); //删除一个数据,删除的是队首元素
QNode LinkQueue::deQueue()
{
//这个就是出队列的动作
QNode e;
//设置两个游标
QNode* p,* q;
//我们首先得判断一下是不是只有一个节点,如果是只有一个有效节点,那么就得更新rear
if (this->getFront()->getNext() == this->getRear())
{
this->setRear(this->getFront()); //设置到头结点上
}
//我们的操作就是把头后面的那个节点拿出来,抛出来
p = this->getFront();
q = p->getNext();
//断裂头和第一个元素的指针,吧指针指向后面一个节点
p->setNext(q->getNext());
q->setNext(nullptr);
//吧q这个节点的值赋值给e然后返回,并回收q的内存
e.setArrivalTime(q->getArrivalTime());
e.setDuration(q->getDuration());
e.setNext(nullptr);
delete q;
return e;
}
//bool queueEmpty(); //判断队列是否为空
bool LinkQueue::queueEmpty()
{
//判断是不是为空的理由就是头和尾是不是指向一个地方
if (this->getFront() == this->getRear())
{
return true;
}
else
{
return false;
}
}
//QNode getHead(); //获取队列的第一个元素,这里只是返回第一个元素的拷贝元素
QNode LinkQueue::getHead()
{
QNode e;
e.setArrivalTime(this->getFront()->getNext()->getArrivalTime());
e.setDuration(this->getFront()->getNext()->getDuration());
e.setNext(nullptr);
return e;
}
我们的主函数:
/*
*功能:假设一个银行有4个窗口对外接待客户。由于每个窗口在某一时刻只能接待一个客户,在客户众多的时候需要排队,对于刚进入银行的客户,如果某个窗口正空闲,
则可上前办理业务,如果所有窗口都不空闲则排在人数最少的窗口。
现在要求模拟银行的某一时间段内的4个窗口的客户排队情况。这里客户到达的时刻和办理业务的时间都是随机的。
**************************************************************************
****这里没有用到线程,其实更好的办法是使用线程,一个线程用来不断产生 ****
****事件(客户的到达),一个线程不断的处理事件,客户的离开,只要银行不 ****
****关门,当事件表为空的时候我们就wait()一个线程,当队列中还有客户的 ****
****时候就吧线程notify,知道银行关门我们就interrupt线程,结束退出 ****
**************************************************************************
*文件:queue.cpp
*时间:2015年7月7日20:33:18,2015年7月8日20:01:31,2015年7月9日20:02:20,2015年7月10日16:45:52,2015年7月12日15:16:41
*作者:cutter_point
*/
#include "Event.h"
#include "LinkList.h"
#include "LinkQueue.h"
#include <iostream>
#include <ctime>
using namespace std;
const static int COUNTER = 5; //一共4个窗口
const static int MAX_TIME = 30; //这个是最大的事务处理时间
const static int INTERVAL = 5; //每五分钟内出现一个客户
LinkList ev; //事件表
Event en;
LinkQueue* q[COUNTER]; //这个设定为指针数组,数组里面的每个元素是一个指向LinkQueue的指针
QNode customer;
int TotalTime, CustomerNum, closeTime = 50;
void open_for_day()
{
cout << "************************************************************************" << endl;
cout << "***** 银行开门 *****" << endl;
cout << "***** 银行开门 *****" << endl;
cout << "************************************************************************" << endl;
//初始化所有的数据
TotalTime = 0; CustomerNum = 0;
en.setOccurTime(0); en.setNType(0);
//初始化所有队列
for (int i = 1; i < COUNTER; ++i)
{
q[i] = new LinkQueue();
}
}//open_for_day()
//求得最短的队列
int min_queue(LinkQueue* q[COUNTER])
{
int max = 0, usei = 1;
for (int i = 1; i < COUNTER; ++i)
{
//循环遍历所有的队列选出最大的那个队列
int testmax = q[i]->length();
if (testmax > max)
{
max = testmax;
usei = i;
}
}
//循环完毕之后max是所有队列中最大的那个
return usei;
}
void customer_arrived()
{
cout << "======================一位客户到达======================" << endl;
//处理客户到达事件,en.NType=0
QNode *qe = new QNode(); //客户到达时间,处理业务所需要的时间,指向下一个对象
int durtime, intertime; //一个是处理业务所需要的时间,一个是下一个客户到达的所需时间
++CustomerNum; //人数++
//参数两个随机数,代表处理业务需要的时间,下一个客户到达的时间
durtime = rand() % (MAX_TIME + 1); //这个额是[0 ~ MAX_TIME(30)]
intertime = rand() % (INTERVAL + 1); //这个是下一个客户进入银行要等待的时间[0~INTERVAL(5)]
//如果银行没有关门,我们要产生下一个事件到达
int t = en.getOccurTime() + intertime;
if (t < closeTime)
{
//插入事件表下一个事件
Event ei;
ei.setOccurTime(t);
ei.setNType(0);
ev.insert(ei);
}
int i = min_queue(q); //求得最短的队列,注意我们有1到4队列,没有0队列
//吧事件插入到最短的队列中
//首先我们的事件发生时间(事件到达时间)和执行时间
qe->setArrivalTime(en.getOccurTime());
qe->setDuration(durtime); //时间的执行时间
q[i]->enQueue(qe); //吧qe插入到链表中
//判断我们插入的队列是不是长度是1,如果是代表在插入之前这个窗口是空的,这个的作用
//客户离开事件,是要首先计算该客户在银行逗留的时间,然后从队列中删除该客户之后查看队列是否为空,若不为空则设定一个新的对头客户离开事件
en.setOccurTime(en.getOccurTime() + durtime); //设置新的时间到达事件
en.setNType(i); //0代表新的客户到达,1是1号窗口客户离开,2是2号窗口客户离开,3是3号窗口客户离开,4是。。。,这里是客户到达事件,所以是0
if (q[i]->length() == 1)
{
//吧第i个队列的离开事件插入事件表,等会用来计算窗口的客户总时间
ev.insert(en);
}
}//customer_arrived()
//处理客户离开事件,是要首先计算该客户在银行逗留的时间,然后从队列中删除该客户之后查看队列是否为空,若不为空则设定一个新的对头客户离开事件
void customer_departure()
{
cout << "======================一位客户离开======================" << endl;
//处理客户离开事件,就是Ntype > 0
int i = en.getNType(); //得到事件的类型
q[i]->deQueue(); //吧排头删除,表示排头客户离开
TotalTime += en.getOccurTime() - customer.getArrivalTime(); //这个是客户逗留事件,不包含处理业务的时间,这个计算的也就是客户等待时间
//如果走了这个客户,队列不是空,则设定一个新的排头客户
if (q[i]->length() != 0)
{
Event e;
customer = q[i]->getHead(); //得到排头的客户
e.setOccurTime(en.getOccurTime() + customer.getDuration()); //客户开始办理业务时间+客户使用时间,也就是到了客户离开时间
e.setNType(i); //那个窗口的
ev.insert(e);
}
}
//这个函数用来产生一系列事件,我们首先产生20个事件
void create_event()
{
for (int i = 1; i < 2; ++i)
{
customer_arrived();
}
}
void bank_smiulation(int closeTime)
{
open_for_day(); //银行开始业务
create_event(); //产生事件
while (!ev.listEmpty()) //只要事件表还没空
{
//Node* p = ev.getHead(); //从事件链表中弹出事件
Node* p = ev.pop();
en = p->getEvent(); //取出时间
if (en.getNType() == 0)
{
//客户到达事件
customer_arrived();
}
else
{
//客户离开
customer_departure();
}
}//while
//计算并输出平均逗留时间
cout << "平均逗留时间是:" << (float)TotalTime/CustomerNum << endl;
}
int main()
{
bank_smiulation(closeTime);
//ev.getHead()->getEvent().getOccurTime();
//cout << "hello world" << ev.getHead()->getEvent().getOccurTime()<<endl;
return 0;
}
实现效果:
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/cutter_point/article/details/47071433