离散事件模型通常需要用到队列和线性表。
典型的例子是银行业务的模拟。
本文参考的是严蔚敏的《数据结构》。
过程如下:用四个队列表示银行的四个窗口,用一个有序链表存储到达事件和离开事件。在初始化函数里面先初始化四个队列和一个链表,并且产生一个到达事件,插在有序链表中。
遍历有序链表,取出头结点,当头结点是到达事件时,随机产生客户的停留时间以及下一客户的到达时间,将下一客户的到达事件插入到有序链表中,将此客户元素插入到最短的队列中,如果队列长度是1,那么为此客户设置一个离开事件。当头结点是离开事件时,删除此队列的头结点,如果此队列不为空,那么给此队列的下一个节点设置离开事件。直到银行工作时间结束。
不过,如果一个客户选择最短队列之后是不能换到其他队列的。
此程序使用vs2015编写的。
#include "stdafx.h"
#include <windows.h>
#include "stdlib.h"
#define Qu 4 // 客户队列数
#define v110 5 // 两相邻到达的客户的时间间隔最大值
#define v120 30 // 每个客户办理业务的时间最大值
typedef struct
{
int OccurTime; // 事件发生时刻
int EventType; // 事件类型,Qu表示到达事件,0至Qu-1表示Qu个窗口的离开事件
}Event, ElemType; //事件类型,有序链表LinkList的数据元素类型
typedef struct
{
int ArrivalTime; // 到达时刻
int Duration; // 办理事务所需时间
}QElemType; // 定义QElemType(队列的数据元素类型)为结构体类型;
typedef struct QNODE
{
QElemType data;
struct QNODE *next;
}QNODE, *PQNODE;
typedef struct
{
PQNODE Front, Rear; // 队头、队尾指针
}LinkQueue;
typedef struct Node // 结点
{
ElemType Data;
struct Node* Flink;
}NODE, *PNODE;
typedef struct LinkList // 链表类型
{
PNODE Head, Tail; // 分别指向线性链表中的头结点和最后一个结点
int Len; // 指示线性链表中数据元素的个数
}LinkList;
void Bank_Simulation();
void OpenForDay();
BOOL InitList(LinkList *L);
BOOL OrderInsert(LinkList *L, ElemType e, int(*comp)(ElemType, ElemType));
int Cmp(Event a, Event b);
BOOL ListEmpty(LinkList L);
BOOL DeleteFirst(LinkList *L, PNODE h, PNODE *q);
PNODE GetListHead(LinkList L);
ElemType GetCurElem(PNODE p);
VOID CustomerArrived();
VOID CustomerDeparture();
int Minimum(LinkQueue Q[]);
BOOL DeQueue(LinkQueue *Q, QElemType *e);
int QueueLength(LinkQueue Q);
BOOL EnQueue(LinkQueue *Q, QElemType e);
BOOL QueueEmpty(LinkQueue Q);
BOOL InitQueue(LinkQueue *Q);
BOOL GetHead_Q(LinkQueue Q, QElemType *e);
void Random(int *d, int *i);
int g_CloseTime; //银行营业时间,单位是分
LinkList g_EventList; // 事件表
Event g_event,g_et;
LinkQueue g_queue[Qu]; // Qu个客户队列
QElemType customer; // 客户记录
int TotalTime = 0, CustomerNum = 0; // 累计客户逗留时间,客户数(初值为0)
int main()
{
printf("请输入银行营业时间长度(单位:分)\n");
scanf("%d", &g_CloseTime);
Bank_Simulation();
return 0;
}
void Bank_Simulation()
{
PNODE p;
OpenForDay(); // 初始化
while (!ListEmpty(g_EventList))
{
DeleteFirst(&g_EventList, GetListHead(g_EventList), &p);
g_event.OccurTime = GetCurElem(p).OccurTime;
g_event.EventType = GetCurElem(p).EventType;
if (g_event.EventType == Qu)
CustomerArrived(); // 处理客户到达事件
else
CustomerDeparture(); // 处理客户离开事件
} // 计算并输出平均逗留时间
printf("顾客总数:%d, 所有顾客共耗时:%d分钟, 平均每人耗时: %d分钟\n", CustomerNum, TotalTime, TotalTime / CustomerNum);
}
void OpenForDay()
{ // 初始化操作
int i;
InitList(&g_EventList); // 初始化事件链表为空
g_event.OccurTime = 0; // 设定第一个客户到达事件
g_event.EventType = Qu; // 到达
OrderInsert(&g_EventList, g_event, Cmp); // 插入事件表
for (i = 0; i<Qu; ++i) // 置空队列
InitQueue(&g_queue[i]);
}
BOOL InitQueue(LinkQueue *Q)
{
// 构造一个空队列Q
(*Q).Front = (*Q).Rear = (PQNODE)malloc(sizeof(QNODE));
if (!(*Q).Front)
{
return FALSE;
}
(*Q).Front->next = NULL;
return TRUE;
}
BOOL InitList(LinkList *L)
{
// 构造一个空的线性链表
PNODE p;
p = (PNODE)malloc(sizeof(Node)); // 生成头结点
if (p!=NULL)
{
p->Flink = NULL;
(*L).Head = (*L).Tail = p;
(*L).Len = 0;
return TRUE;
}
else
{
return FALSE;
}
}
BOOL OrderInsert(LinkList *L, ElemType e, int(*comp)(ElemType, ElemType))
{ // 已知L为有序线性链表,将元素e按非降序插入在L中。(用于一元多项式)
PNODE v1, v2, v3;
v3 = (*L).Head;
v2 = v3->Flink;
while (v2 != NULL&&comp(v2->Data, e)<0) // v2不是表尾且元素值小于e
{
v3 = v2;
v2 = v2->Flink;
}
v1 = (PNODE)malloc(sizeof(Node)); // 生成结点
v1->Data = e; // 赋值
v3->Flink = v1; // 插入
v1->Flink = v2;
(*L).Len++; // 表长加1
if (!v2) // 插在表尾
(*L).Tail = v1; // 修改尾结点
return TRUE;
}
int Cmp(Event a, Event b)
{ // 依事件a的发生时刻<、=或>事件b的发生时刻分别返回-1、0或1
if (a.OccurTime == b.OccurTime)
return 0;
else
return (a.OccurTime - b.OccurTime) / abs(a.OccurTime - b.OccurTime);
//abs函数是求绝对值
}
BOOL ListEmpty(LinkList L)
{ // 若线性链表L为空表,则返回TRUE,否则返回FALSE
if (L.Len)
return FALSE;
else
return TRUE;
}
BOOL DeleteFirst(LinkList *L, PNODE h, PNODE *q)
{ // h指向L的一个结点,把h当做头结点,删除链表中的第一个结点并以q返回。
// 若链表为空(h指向尾结点),q=NULL,返回FALSE
*q = h->Flink;
if (*q) // 链表非空
{
h->Flink = (*q)->Flink;
if (!h->Flink) // 删除尾结点
(*L).Tail = h; // 修改尾指针
(*L).Len--;
return TRUE;
}
else
return FALSE; // 链表空
}
PNODE GetListHead(LinkList L)
{
return L.Head;
}
ElemType GetCurElem(PNODE p)
{ // 已知p指向线性链表中的一个结点,返回p所指结点中数据元素的值
return p->Data;
}
VOID CustomerArrived()
{ // 处理客户到达事件
// 生成该客户的停留时间以及下一客户的到达时刻,将下一客户到达事件插入事件表中,
// 并将此客户插入最短队列中,如果此队列的长度是1,那么设定一个离开事件并插入事件表中。
QElemType f;
int durtime, intertime, i;
++CustomerNum;
Random(&durtime, &intertime); // 生成随机数
g_et.OccurTime = g_event.OccurTime + intertime; // 下一客户到达时刻
g_et.EventType = Qu; // 队列中只有一个客户到达事件
if (g_et.OccurTime<g_CloseTime) // 银行尚未关门,插入事件表
OrderInsert(&g_EventList, g_et, Cmp);
i = Minimum(g_queue); // 求长度最短队列的序号,等长为最小的序号
f.ArrivalTime = g_event.OccurTime;
f.Duration = durtime;
EnQueue(&g_queue[i], f);
if (QueueLength(g_queue[i]) == 1)
{
g_et.OccurTime = g_event.OccurTime + durtime;
g_et.EventType = i;
OrderInsert(&g_EventList, g_et, Cmp); // 设定第i队列的一个离开事件并插入事件表
}
}
VOID CustomerDeparture()
{ // 处理客户离开事件
// 删除第i队列的排头客户,在第i队列不为空的情况下,设定第i队列的下一个离开事件
int i;
i = g_event.EventType;
DeQueue(&g_queue[i], &customer); // 删除第i队列的排头客户
TotalTime += g_event.OccurTime - customer.ArrivalTime; // 累计客户逗留时间
if (!QueueEmpty(g_queue[i]))
{ // 设定第i队列的一个离开事件并插入事件表
GetHead_Q(g_queue[i], &customer);
g_et.OccurTime = g_event.OccurTime + customer.Duration;
g_et.EventType= i;
OrderInsert(&g_EventList, g_et, Cmp);
}
}
int Minimum(LinkQueue Q[]) // 返回最短队列的序号
{
int l[Qu];
int i, k;
for (i = 0; i<Qu; i++)
l[i] = QueueLength(Q[i]);
k = 0;
for (i = 1; i<Qu; i++)
if (l[i]<l[0])
{
l[0] = l[i];
k = i;
}
return k;
}
BOOL DeQueue(LinkQueue *Q, QElemType *e)
{ // 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR
PQNODE p;
if ((*Q).Front == (*Q).Rear)
return FALSE;
p = (*Q).Front->next;
*e = p->data;
(*Q).Front->next = p->next;
if ((*Q).Rear == p)
(*Q).Rear = (*Q).Front;
free(p);
return TRUE;
}
BOOL EnQueue(LinkQueue *Q, QElemType e)
{ // 插入元素e为Q的新的队尾元素
PQNODE p = (PQNODE)malloc(sizeof(QNODE));
if (!p) // 存储分配失败
{
return FALSE;
}
p->data = e;
p->next = NULL;
(*Q).Rear->next = p;
(*Q).Rear = p;
return TRUE;
}
int QueueLength(LinkQueue Q)
{ // 求队列的长度
int i = 0;
PQNODE p;
p = Q.Front;
while (Q.Rear != p)
{
i++;
p = p->next;
}
return i;
}
BOOL QueueEmpty(LinkQueue Q)
{ // 若Q为空队列,则返回TRUE,否则返回FALSE
if (Q.Front == Q.Rear)
return TRUE;
else
return FALSE;
}
BOOL GetHead_Q(LinkQueue Q, QElemType *e)
{ // 若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR
PQNODE p;
if (Q.Front == Q.Rear)
return FALSE;
p = Q.Front->next;
*e = p->data;
return TRUE;
}
void Random(int *d, int *i)
{
*d = rand() % v120 + 1; // 1到v120之间的随机数
*i = rand() % v110 + 1; // 1到v110之间的随机数
}