数据结构队列 离散事件模拟 银行排队问题
【数据结构】 队列 离散事件模拟 银行排队问题
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; //队列的数据元素类型
其他函数
#pragma once #include"..\..\Project1\Project1\LinkQueue.h" #include"C:\Users\lwww\source\repos\线性链表\线性链表\List.h" //基本结构体定义 typedef LinkList EvenList ;//事件链表类型,定义为有序链表 //主要变量 EvenList ev; //事件表 Event en; //事件 LinkQueue q[5]; //四个客户队列 int TotalTime, CustomerNum; //累计客户逗留时间,客户数 int CloseTime = 600; int cmp(Event a, Event b) { if (a.OccurTime < b.OccurTime)return -1; if (a.OccurTime == b.OccurTime)return 0; if (a.OccurTime > b.OccurTime)return 1; } void OrderInsert(EvenList ev, Event en, int (*cmp)(Event a,Event b)) { //定义事件插入操作,按发生时间非递减排序 Link w; w = ev.head; int i = 1; while (w->next&&cmp(en, w->next->data) > 0) { w = w->next; i++; } ListInsert_L(ev, i, en); } int Minmum(LinkQueue q[5]) { int minlenth = QueueLenth(q[1]); int i=1; for (int j = 2; j < 5; j++) { if (minlenth == 0) return i; if (minlenth > QueueLenth(q[j])) { minlenth = QueueLenth(q[j]); i = j; } } return i; }
List.h
该代码依照书P37页写成,有很多函数在此处用不到。
代码可能存在一些问题,因为书中一些函数的定义本身有问题,主要是关于头尾指针的问题,修改后该程序可以正常运行(修改了很多次)
#include<stdio.h> #include<stdlib.h> #include<iostream> using namespace std; #define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #define INFEASLBLE -1 typedef int Status; typedef struct { int OccurTime; //事件发生时刻 int NType; //事件类型,0表示到达事件,1-4表示4个窗口的离开事件 }Event, ElemType; //事件类型,有序链表LinkList的数据元素类型 typedef struct LNode { //结点类型 ElemType data; struct LNode *next; }*Link, *Position; typedef struct { //链表类型 Link head, tail; //头结点和末尾节点 int len;//指示线性链表中元素个数 }LinkList; //分配由p指向的值为e的结点,并返回OK;若分配失败,则返回ERROR Status MakeNode(Link &p, ElemType e); //释放p所指结点 void FreeNode(Link &p); //构造一个空的线性表L Status InitList(LinkList &L); //销毁线性表L,L不再存在 Status DestroyList(LinkList &L); //将线性表L置空,并释放原链表的结点空间 Status ClearList(LinkList &L); //已知h指向线性表的头结点,将s所指结点插入在第一个结点之前 //无法修改len Status InsFirst(Link h, Link s); // 已知h指向线性表的头结点,删除链表中第一个元素并用结点q返回 //无法修改len Status DelFirst(Link h, Link &q); //将指针s所指的一串结点链接在线性表L的最后一个结点;并改变链表L的尾指针指向新的尾结点 Status Append(LinkList &L, Link s); //删除线性链表L中的尾结点并以q返回,改变链表中尾结点 Status Remove(LinkList &L, Link &q); //已知p指向线性链表中的一个结点,将s所指结点插入在p所指结点之前,修改指针p指向新的插入点 Status InsBefore(LinkList &L, Link &p, Link s); // 已知p指向线性链表中的一个结点,将s所指结点插入在p所指结点之后,修改指针p指向新的插入点 Status InsAfter(LinkList &L, Link &p, Link s); //已知p指向线性链表中的一个结点,用e更新p所指结点中元素的值 Status SetCurElem(Link &p, ElemType e); //已知p指向线性链表中的一个结点,返回p所指结点中元素的值 ElemType GetCurElem(Link p); //若线性链表为空表,则返回TRUE,否则返回FALSE Status ListEmpty(LinkList L); //返回链表中元素个数 int ListLenth(LinkList L); //返回线性链表L中头结点的位置 Position GetHead(LinkList L); //返回线性链表中最后一个结点的位置; Position Getlast(LinkList L); //返回P所指结点的直接前驱 Position PriorPos(LinkList L, Link p); //返回P所指结点的直接后继 Position NextPos(LinkList L, Link p); //返回p指示链表中第i个结点的位置 Status LocatePos(LinkList L, int i, Link &p); //返回线性链表中第一个与e满足函数关系compare的元素位置 Position LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType)); //依次对L中每个元素调用函数visit() Status ListTraverse(LinkList L, Status(*visit)(Link)); //在i处插入值为e的元素 Status ListInsert_L(LinkList &L, int i, ElemType e);
List.c
#include<stdio.h> #include<stdlib.h> #include<iostream> #include"List.h" using namespace std; Status MakeNode(Link &p, ElemType e) { p = (Link)malloc(sizeof(LNode)); if (!p) return ERROR; p->data = e; p->next = NULL; return OK; } void FreeNode(Link &p) { free(p); } Status InitList(LinkList &L) { L.head = (Link)malloc(sizeof(LNode)); L.tail = (Link)malloc(sizeof(LNode)); if (!L.head || !L.tail)return ERROR; L.len = 0; L.tail = L.head; L.head->next = NULL; //L.tail->next = L.head; return OK; } Status DestroyList(LinkList &L) { Link p; while(L.head->next){ p = L.head->next; free(L.head); L.head = p; } L.len = 0; free(L.head); free(L.tail); return OK; } Status ClearList(LinkList &L) { Link p; p = L.head->next; Link q; while (p) { q = p->next; free(p); p = q; } L.head->next = L.tail; L.len = 0; return OK; } Status InsFirst(Link h, Link s) { Link p; p = h->next; h->next = s; s->next = p; return OK; } Status DelFirst(Link h, Link &q) { Link p; p = (Link)malloc(sizeof(LNode)); if (!h->next)return ERROR; p = h->next; q = p; h->next = p->next; //free(p); return OK; } Status Append(LinkList &L, Link s) { if (!s) { return ERROR; } Link p; p = L.tail; //p为末尾结点 p->next = s; //将q串入L int j = 1; while (s->next) { s = s->next; j++; } L.tail = s; //改变尾指针 L.len += j; //修改表长 return OK; } Status Remove(LinkList &L, Link &q) { if (!L.tail) { return ERROR; } Link p; p = L.head; q = L.tail; while (p->next->next) { p = p->next; } L.tail = p; //修改尾指针 p->next = NULL; L.len--; return OK; } Status InsBefore(LinkList &L, Link &p, Link s) { //已知p指向线性链表中的一个结点,将s所指结点插入在p所指结点之前,修改指针p指向新的插入点 Link q; q = L.head; while (!(q->next==p)) { if (!q->next)return ERROR; q = q->next; } q->next = s; s->next = p; p = s; L.len++; return OK; } Status InsAfter(LinkList &L, Link &p, Link s) { Link q; q = (Link)malloc(sizeof(LNode)); if (p->next) { //P不指向最后一个结点 q = p->next; s->next = q; p->next = s; } else { //p指向最后一个结点 p->next = s; L.tail = s; //尾指针指向s } free(q); L.len++; return OK; } Status SetCurElem(Link &p, ElemType e) { if (!p)return ERROR; p->data = e; return OK; } ElemType GetCurElem(Link p) { return p->data; } Status ListEmpty(LinkList L){ if (!L.head->next)return TRUE; return FALSE; } int ListLenth(LinkList L) { return L.len; } Position GetHead(LinkList L) { return L.head; } Position Getlast(LinkList L) { return L.tail; } Status LocatePos(LinkList L, int i, Link &p) { Link q; q = L.head; int j = 0; while (j < i&&q) { q = q->next; j++; } if (j > i || !q) return ERROR; p = q; return OK; } Position PriorPos(LinkList L, Link p) { Link q; q = L.head; if (p == q->next) return ERROR; while (!(q->next == p)) { q = q->next; } return q; } Position NextPos(LinkList L, Link p) { if (!p->next)return ERROR; return p->next; } Position LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) { Link p; p = L.head->next; while (!compare(p->data, e)) { p = p->next; if (!p)return ERROR; } return p; } Status ListTraverse(LinkList L, Status(*visit)(Link)) { Link p; p = L.head->next; int j = 0; while (p) { if (!visit(p))return ERROR; p = p->next; } return OK; } Status visit(Link p) { return OK; } Status ListInsert_L(LinkList &L, int i, ElemType e) { Link h; Link s; h = (Link)malloc(sizeof(LNode)); if (!LocatePos(L, i - 1, h))return ERROR; if (!MakeNode(s, e)) return ERROR; InsFirst(h, s); L.len++; return OK; }
优化模型的附加代码:
1.在LinkQueue中增加一个删除队尾元素操作
Status DerearQueue(LinkQueue &Q, QElemType &e) { //若队列不空,删除Q的队尾元素,用e返回其值,并返回OK if (Q.front == Q.rear)return ERROR; QueuePtr p; p = Q.front; while (!(p->next == Q.rear))p = p->next; e = Q.rear->data; free(Q.rear); Q.rear = p; }
2.增加一个寻找最大长度队列函数
若最大长度小于2,返回0,此时不需要插队操作
int Maxmum(LinkQueue q[5]) { int maxlenth = QueueLenth(q[1]); int i = 1; for (int j = 2; j < 5; j++) { if (maxlenth < QueueLenth(q[j])) { maxlenth = QueueLenth(q[j]); i = j; } } if (maxlenth >= 2) return i; else return 0; }
3.修改客户离开操作,增加更换队列操作
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队列的离开事件 } else{ //如果离开后该队列为空,判断是否进行换队列操作 int k = Maxmum(q); if (k>0) { //K>0,则进行换队列操作 DerearQueue(q[k], customer); EnQueue(q[i], customer); OrderInsert(ev, { en.OccurTime + customer.Duration, i }, cmp); //更换队伍后插入一个离开事件 } } }//CustomerDeparture
- 严蔚敏 数据结构C语言 银行排队队列 离散事件模拟
- c语言版数据结构(奇迹冬瓜)-队列实战(1)离散事件模拟(银行排队)
- 数据结构学习笔记 --- 队列的应用举例(离散事件模拟)
- 离散事件模拟-银行管理(模拟题,队列)
- 数据结构作业2 ------ 用队列模拟银行排队的情况
- 队列应用银行排队问题模拟:计算客户的平均停留时间和等待时间以及每个客户的时间信息,两种方法实现
- PTA 银行排队问题之单队列多窗口加VIP服务 队列+模拟
- 3-9-模拟银行排队过程-栈和队列-第3章-《数据结构》课本源码-严蔚敏吴伟民版
- 离散事件模拟--银行排队时间模拟
- 3-9-模拟银行排队过程-栈和队列-第3章-《数据结构》课本源码-严蔚敏吴伟民版
- 离散事件模拟-银行管理 分类: 栈和队列 2015-06-18 17:10 21人阅读 评论(0) 收藏
- 离散事件模拟-银行管理——队列思想(双队列)
- 队列->离散事件模拟-银行管理
- 统计工龄 模拟EXCEL排序 银行排队问题之单队列多窗口问题 银行业务队列简单模拟 堆栈操作合法性 两个有序序列的中位数
- 离散事件模拟-银行管理 模拟,队列
- 离散事件模拟-银行管理(队列)
- 事件驱动编程---队列应用--银行排队模拟--学习与思考
- PTA 7-1 银行排队问题之单队列多窗口服务(25 分)25分代码 (小模拟)
- PAT 银行排队问题之单窗口“夹塞”版 (队列+模拟) -- 解题报告
- SDUT 2087 离散事件模拟-银行管理(队列模拟)