数据结构队列 离散事件模拟 银行排队问题
【数据结构】 队列 离散事件模拟 银行排队问题
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