1. 引言
本文主要讲解一个队列和线性表的例子——离散事件模拟。
2. 离散事件模拟——银行业务模拟
#include "ds.h"
#define FILE_TEST
// 银行业务模拟
#define Qu 4 // 客户队列数
#define Khjg 5 // 两相邻到达的客户的时间间隔最大值
#define Blsj 30 // 每个客户办理业务的时间最大值
typedef struct
{
int OccurTime; // 事件发生的时间
int NType; // 事件类型, Qu表示到达事件,0至Qu-1表示Qu个窗口的离开事件
}Event, ElemType; // 事件类型,有序链表LinkList的数据元素类型
typedef struct LNode
{
ElemType data;
LNode *next;
}LNode, *Link, *Position;;
typedef struct
{
Link head, tail;
int len;
}LinkList;
typedef LinkList EventList; //事件链表指针类型,定义为有序链表
typedef struct
{
int ArrivalTime; // 到达时间
int Duration; // 办理事件
}QElemType; // 定义队列的数据元素类型
// 单链队列--队列的链式存储结构
typedef struct QNode
{
QElemType data;
QNode *next;
}*QueuePtr;
typedef struct
{
QueuePtr front, rear; // 队头、队尾指针
}LinkQueue;
// 程序中用到得主要变量(全局)
EventList ev; // 事件表头指针
Event en, et; // 事件, 临时变量
#ifdef FILE_TEST
FILE *fp; // 文件类型指针,用于指向b.txt, a,txt
#endif
long int TotalTime = 0; // 累计客户逗留时间(初值为0)
int CloseTime, CustomerNum = 0; // 银行营业时间(单位为分),客户数(初值为0)
void MakeNode(Link &p, ElemType e);
void FreeNode(Link &p);
void InitList(LinkList &L);
void ClearList(LinkList &L);
void DestroyList(LinkList &L);
// h指向L的一个结点,把h当做头结点,将s所指结点插入在第一个结点之前, 形参增加L,因为需修改L
void InsFirst(LinkList &L,Link h,Link s);
// h指向L的一个结点,把h当做头结点,删除链表中的第一个结点并以q返回。
// 若链表为空(h指向尾结点),q=NULL,返回FALSE
Status DelFirst(LinkList &L, Link h, Link &q);
// 将指针s(s->data为第一个数据元素)所指(彼此以指针相链,以NULL结尾)的
// 一串结点链接在线性链表L的最后一个结点之后,并改变链表L的尾指针指向新的尾结点
void Append(LinkList &L, Link s);
// 已知p指向线性链表L中的一个结点,返回p所指结点的直接前驱的位置。若无前驱,则返回NULL
Position PriorPos(LinkList L, Link p);
// 删除线性链表L中的尾结点并以q返回,改变链表L的尾指针指向新的尾结点
Status Remove(LinkList &L, Link &q);
// 已知p指向线性链表L中的一个结点,将s所指结点插入在p所指结点之前,
// 并修改指针p指向新插入的结点
void InsBefore(LinkList &L, Link &p, Link s);
// 已知p指向线性链表L中的一个结点,将s所指结点插入在p所指结点之后,
// 并修改指针p指向新插入的结点
void InsAfter(LinkList &L,Link &p,Link s);
// 已知p指向线性链表中的一个结点,用e更新p所指结点中数据元素的值
void SetCurElem(Link p, ElemType e);
ElemType GetCurElem(Link p);
Status ListEmpty(LinkList L);
int ListLength(LinkList L);
Position GetHead(LinkList L);
Position GetLast(LinkList L);
Position NextPos(Link p);
// 返回p指示线性链表L中第i个结点的位置,并返回OK,i值不合法时返回ERROR。i=0为头结点
Status LocatePos(LinkList L, int i, Link &p);
// 返回线性链表L中第1个与e满足函数compare()判定关系的元素的位置,
// 若不存在这样的元素,则返回NULL
Position LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType));
void ListTraverse(LinkList L, void(*visit)(ElemType) );
// 已知L为有序线性链表,将元素e按非降序插入在L中。
void OrderInsert(LinkList &L, ElemType e, int (*com)(ElemType, ElemType));
// 若升序链表L中存在与e满足判定函数compare()取值为0的元素,则q指示L中
// 第一个值为e的结点的位置,并返回TRUE;否则q指示第一个与e满足判定函数
// compare()取值>0的元素的前驱的位置。并返回FALSE。(用于一元多项式)
Status LocateElem(LinkList L,ElemType e,Position &q,int(*compare)(ElemType,ElemType));
// 分配由p指向的值为e的结点。若分配失败,则退出
void MakeNode(Link &p, ElemType e)
{
p = (Link)malloc(sizeof(LNode));
if (!p)
exit(ERROR);
memcpy(&(p->data), &e, sizeof(ElemType));
}
// 释放p所指结点
void FreeNode(Link &p)
{
free(p);
p = NULL;
}
// 构造一个空的线性链表L
void InitList(LinkList &L)
{
Link p;
p = (Link)malloc(sizeof(LNode)); // 生成头结点
if (p)
{
p->next = NULL;
L.head = L.tail = p;
L.len = 0;
}
else
exit(ERROR);
}
// 将线性链表L重置为空表,并释放原链表的结点空间
void ClearList(LinkList &L)
{
Link p, q;
if (L.head != L.tail)
{
p = q = L.head->next;
L.head->next = NULL;
while (p != L.tail)
{
q = p->next;
free(p);
p = q;
}
free(q); // 释放尾节点
L.tail = L.head;
L.len = 0;
}
}
// 销毁线性链表L,L不再存在
void DestroyList(LinkList &L)
{
ClearList(L);
FreeNode(L.head);
L.tail = NULL;
L.len = 0;
}
// h指向L的一个结点,把h当做头结点,将s所指结点插入在第一个结点之前, 形参增加L,因为需修改L
void InsFirst(LinkList &L,Link h,Link s)
{
s->next = h->next;
h->next = s;
if (h == L.tail)
L.tail = s;
L.len++;
}
// h指向L的一个结点,把h当做头结点,删除链表中的第一个结点并以q返回。
// 若链表为空(h指向尾结点),q=NULL,返回FALSE
Status DelFirst(LinkList &L, Link h, Link &q)
{
q = h->next;
if (q) // 链表非空
{
h->next = q->next;
if(!h->next) // 删除尾结点
L.tail = h; // 修改尾指针
L.len--;
return OK;
}
else
return FALSE; // 链表空
}
// 将指针s(s->data为第一个数据元素)所指(彼此以指针相链,以NULL结尾)的
// 一串结点链接在线性链表L的最后一个结点之后,并改变链表L的尾指针指向新的尾结点
void Append(LinkList &L, Link s)
{
int i = 1;
Link p = s;
if (NULL == p)
return;
L.tail->next = s;
while (p->next)
{
i++;
p = p->next;
}
L.tail = p;
L.len += i;
}
// 已知p指向线性链表L中的一个结点,返回p所指结点的直接前驱的位置。若无前驱,则返回NULL
Position PriorPos(LinkList L, Link p)
{
Link s = L.head->next;
if (p == NULL || p == L.head || p == s)
return NULL;
while (s->next)
{
if (p == s->next)
return s;
s = s->next;
}
}
// 删除线性链表L中的尾结点并以q返回,改变链表L的尾指针指向新的尾结点
Status Remove(LinkList &L, Link &q)
{
Link p=L.head;
if(L.len==0) // 空表
{
q=NULL;
return FALSE;
}
while(p->next!=L.tail)
p=p->next;
q=L.tail;
p->next=NULL;
L.tail=p;
L.len--;
return OK;
}
// 已知p指向线性链表L中的一个结点,将s所指结点插入在p所指结点之前,
// 并修改指针p指向新插入的结点
void InsBefore(LinkList &L, Link &p, Link s)
{
Link temp = L.head;
while (temp->next != p)
{
temp = temp->next;
}
s->next = temp->next;
temp->next = s;
p = s;
L.len++;
}
// 已知p指向线性链表L中的一个结点,将s所指结点插入在p所指结点之后,
// 并修改指针p指向新插入的结点
void InsAfter(LinkList &L,Link &p,Link s)
{
if (p == L.tail)
{
p->next = s;
s->next = NULL;
L.tail = s;
}
else
{
s->next = p->next;
p->next = s;
}
p = s;
L.len++;
}
// 已知p指向线性链表中的一个结点,用e更新p所指结点中数据元素的值
void SetCurElem(Link p, ElemType e)
{
memcpy(&(p->data), &e, sizeof(sizeof(ElemType)));
}
ElemType GetCurElem(Link p)
{
return p->data;
}
Status ListEmpty(LinkList L)
{
if (0 == L.len)
return TRUE;
else
return FALSE;
}
int ListLength(LinkList L)
{
return L.len;
}
Position GetHead(LinkList L)
{
return L.head;
}
Position GetLast(LinkList L)
{
return L.tail;
}
Position NextPos(Link p)
{ // 已知p指向线性链表L中的一个结点,返回p所指结点的直接后继的位置。若无后继,则返回NULL
return p->next;
}
// 返回p指示线性链表L中第i个结点的位置,并返回OK,i值不合法时返回ERROR。i=0为头结点
Status LocatePos(LinkList L, int i, Link &p)
{
int j = 0;
p = L.head;
while (j < i && p != NULL)
{
j++;
p = p->next;
}
if (j > i || !p )
return ERROR;
return OK;
}
// 返回线性链表L中第1个与e满足函数compare()判定关系的元素的位置,
// 若不存在这样的元素,则返回NULL
Position LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType))
{
Link p = L.head->next;
while (p && !compare(e, p->data))
p = p->next;
return p;
}
void ListTraverse(LinkList L, void(*visit)(ElemType) )
{
Link p = L.head->next;
while (p)
{
visit(p->data);
p = p->next;
}
printf("\n");
}
// 已知L为有序线性链表,将元素e按非降序插入在L中。
void OrderInsert(LinkList &L, ElemType e, int (*com)(ElemType, ElemType))
{
Link o,p,q;
q=L.head;
p=q->next;
while(p!=NULL&&com(p->data,e)<0) // p不是表尾且元素值小于e
{
q=p;
p=p->next;
}
o=(Link)malloc(sizeof(LNode)); // 生成结点
o->data=e; // 赋值
q->next=o; // 插入
o->next=p;
L.len++; // 表长加1
if(!p) // 插在表尾
L.tail=o; // 修改尾结点
}
// 若升序链表L中存在与e满足判定函数compare()取值为0的元素,则q指示L中
// 第一个值为e的结点的位置,并返回TRUE;否则q指示第一个与e满足判定函数
// compare()取值>0的元素的前驱的位置。并返回FALSE。(用于一元多项式)
Status LocateElem(LinkList L,ElemType e,Position &q,int(*compare)(ElemType,ElemType))
{
Link p=L.head,pp;
do
{
pp=p;
p=p->next;
}while(p&&(compare(p->data,e)<0)); // 没到表尾且p->data.expndata,e)>0) // 到表尾或compare(p->data,e)>0
{
q=pp;
return FALSE;
}
else // 找到
{
q=p;
return TRUE;
}
}
// 单链队列
void InitQueue(LinkQueue &Q);
void DestroyQueue(LinkQueue &Q);
void ClearQueue(LinkQueue &Q);
Status QueueEmpty(LinkQueue Q);
int QueueLength(LinkQueue Q);
Status GetHead(LinkQueue Q, QElemType &e);
void EnQueue(LinkQueue &Q, QElemType e);
Status DeQueue(LinkQueue &Q, QElemType &e);
void QueueTraverse(LinkQueue Q, void(*vi)(QElemType));
// 带头结点的单链队列
void InitQueue(LinkQueue &Q)
{
Q.front = (QueuePtr)malloc(sizeof(QNode));
if (!Q.front) exit(OVERFLOW);
Q.front->next = NULL;
Q.rear = Q.front;
}
void DestroyQueue(LinkQueue &Q)
{
QueuePtr q, p = Q.front;
while (p)
{
q = p->next;
free(p);
p = q;
}
Q.front = Q.rear = NULL;
}
void ClearQueue(LinkQueue &Q)
{
QueuePtr q, p = Q.front->next;
while (p)
{
q = p->next;
free(p);
p = q;
}
Q.front->next = NULL;
Q.rear = Q.front;
}
Status QueueEmpty(LinkQueue Q)
{
if (Q.front == Q.rear)
return TRUE;
else
return FALSE;
}
int QueueLength(LinkQueue Q)
{
int i = 0;
QueuePtr p = Q.front->next;
while (p)
{
i++;
p = p->next;
}
return i;
}
Status GetHead(LinkQueue Q, QElemType &e)
{
if (Q.front->next)
{
memcpy(&e, &(Q.front->next->data), sizeof(QElemType));
return OK;
}
else
{
return FALSE;
}
}
void EnQueue(LinkQueue &Q, QElemType e)
{
QueuePtr p = (QueuePtr)malloc(sizeof(QNode));
if (!p) exit(OVERFLOW);
p->next = NULL;
memcpy(&(p->data), &e, sizeof(QElemType));
Q.rear->next = p;
Q.rear = p;
}
Status DeQueue(LinkQueue &Q, QElemType &e)
{
QueuePtr p = Q.front, q;
if (Q.front == Q.rear)
return FALSE;
q = p->next;
memcpy(&e, &(q->data), sizeof(QElemType));
p->next = q->next;
if (Q.rear == q)
Q.rear = Q.front;
free(q);
return OK;
}
void QueueTraverse(LinkQueue Q, void(*vi)(QElemType))
{
QueuePtr p = Q.front->next;
while (p)
{
vi(p->data);
p = p->next;
}
printf("\n");
}
// 依事件a的发生时刻<、=或>事件b的发生时刻分别返回-1、0或1
int com(Event a, Event b)
{
if (a.OccurTime == b.OccurTime)
return 0;
else
return ((a.OccurTime - b.OccurTime) / abs(a.OccurTime - b.OccurTime));
}
// 生成两个随机数
void Random(int &d, int &i)
{
d = rand()%Blsj + 1; // 1到Bljs之间的随机数(办理业务的时间)
i = rand()%Khjg + 1; // 0到Khjg之间的随机数(客户到达的时间间隔)
}
void OpenForDay();
void CustomerArrived();
void CustomerDeparture();
// 银行业务模拟函数
void Bank_Simulation()
{
Link p;
OpenForDay(); // 初始化事件表ev且插入第一个到达时间, 初始化队列
while (!ListEmpty(ev)) //表事件不为空
{
DelFirst(ev, ev.head, p);
#ifdef FILE_TEST
if (p->data.OccurTime < 50) // 输出前50分钟内发生的事件到文件d.txt
fprintf(fp, "p->data.OccurTime = %3d p->data.NType = %d\n", p->data.OccurTime, p->data.NType);
#endif
en.OccurTime = GetCurElem(p).OccurTime;
en.NType = GetCurElem(p).NType;
if (en.NType == Qu) // 到达事件
CustomerArrived(); // 处理客户到达事件
else // 由某个窗口离开的事件
CustomerDeparture();
}
// 计算并输出平均逗留时间
printf("窗口数=%d 两相邻到达的客户的时间间隔=0 - %d 分钟 每个客户办理业务的时间=1 - %d 分钟\n", Qu, Khjg, Blsj);
printf("客户总数:%d, 所有客户共耗时:%ld 分钟, 平均每人耗时:%d 分钟,", CustomerNum, TotalTime, TotalTime/CustomerNum);
printf("最后一个客户离开的时间:%d 分\n", en.OccurTime);
}
// 银行业务模拟
LinkQueue q[Qu]; // Qu 个客户队列
QElemType customer; // 客户记录, 临时变量
#ifdef FILE_TEST
FILE *fq; // 文件型指针,用于指向a.txt文件
#endif
// 初始化事件表ev且插入第一个到达时间, 初始化队列
void OpenForDay()
{
int i;
InitList(ev); // 初始化事件链表ev为空
en.OccurTime = 0; // 设定第1位客户到达时间为0(银行一开门,就有客户来)
#ifdef FILE_TEST
fprintf(fq,"首位客户到达时刻=%3d,",en.OccurTime);
#endif
en.NType=Qu; // 到达
OrderInsert(ev,en,com); // 将第1个到达事件en有序插入事件表ev中
for(i=0;i