【数据结构】3、模拟银行窗口排队叫号系统——C++

来源:互联网 发布:网络解锁nck 编辑:程序博客网 时间:2024/05/01 11:34

这里我们模拟一下银行排队叫号系统的实现:


假设一个银行有4个窗口对外接待客户。由于每个窗口在某一时刻只能接待一个客户,在客户众多的时候需要排队,对于刚进入银行的客户,如果某个窗口正空闲,
则可上前办理业务,如果所有窗口都不空闲则排在人数最少的窗口。
现在要求模拟银行的某一时间段内的4个窗口的客户排队情况。这里客户到达的时刻和办理业务的时间都是随机的。



首先我们银行发生事件,我们得有一个类表示事件对象

/**功能:这个实现的是我们事件的数据单元节点*文件:Event.h*时间:2015年7月8日20:55:32*作者:cutter_point*/#ifndef EVENT_H#define EVENT_Hclass 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_Hclass 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;//我们首先得判断一下是不是只有一个节点,如果是只有一个有效节点,那么就得更新rearif (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=0QNode *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是。。。,这里是客户到达事件,所以是0if (q[i]->length() == 1){//吧第i个队列的离开事件插入事件表,等会用来计算窗口的客户总时间ev.insert(en);}}//customer_arrived()//处理客户离开事件,是要首先计算该客户在银行逗留的时间,然后从队列中删除该客户之后查看队列是否为空,若不为空则设定一个新的对头客户离开事件void customer_departure(){cout << "======================一位客户离开======================" << endl;//处理客户离开事件,就是Ntype > 0int 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;}


实现效果:












1 0
原创粉丝点击