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.expn<e.expn if(!p||compare(p->data,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<Qu;++i) // 初始化Qu个队列 InitQueue(q[i]); } // 返回最短队列的序号,若有并列值,返回队列序号最小的 int Minimum(LinkQueue Q[]) { int l[Qu]; int i,k=0; for(i=0;i<Qu;i++) l[i]=QueueLength(Q[i]); for(i=1;i<Qu;i++) if(l[i]<l[0]) { l[0] = l[i]; k = i; } return k; } // 处理客户到达事件en(en.NType=Qu) void CustomerArrived() { QElemType f; int durtime,intertime,i; ++CustomerNum; // 客户数加1 Random(durtime,intertime); // 生成当前客户办理业务的时间和下一个客户到达的时间间隔2个随机数 et.OccurTime = en.OccurTime + intertime; // 下一客户et到达时刻=当前客户en的到达时间+时间间隔 et.NType = Qu; // 下一客户到达事件 i = Minimum(q); // 求长度最短队列的序号,等长为最小的序号(到达事件将入该队) #ifdef FILE_TEST if(CustomerNum<=20) // 输出前20个客户到达信息到文件a.txt中 fprintf(fq,"办理业务的时间=%2d,所排队列=%d\n下一客户到达时刻=%3d,",durtime,i,et.OccurTime); #endif if(et.OccurTime < CloseTime) // 下一客户到达时银行尚未关门 OrderInsert(ev,et,com); // 按升序将下一客户到达事件et插入事件表ev中,在bo2-6.cpp中 f.ArrivalTime = en.OccurTime; // 将当前客户到达事件en赋给队列元素f f.Duration = durtime; EnQueue(q[i],f); // 将f入队到第i队列(i=0~Qu-1) if(QueueLength(q[i])==1) // 该元素为队头元素 { et.OccurTime=en.OccurTime+durtime; // 设定一个离开事件et et.NType=i; OrderInsert(ev,et,com); // 将此离开事件et按升序插入事件表ev中 } } // 处理客户离开事件en(en.NType<Qu) void CustomerDeparture() { int i; i = en.NType; // 确定离开事件en发生的队列序号i DeQueue(q[i],customer); // 删除第i队列的排头客户 TotalTime += en.OccurTime - customer.ArrivalTime; // 客户逗留时间=离开事件en的发生时刻-该客户的到达时间 if(!QueueEmpty(q[i])) { // 删除第i队列的排头客户后,第i队列仍不空 GetHead(q[i],customer); // 将第i队列新的排头客户赋给customer et.OccurTime = en.OccurTime+customer.Duration; // 设定离开事件et,新排头的离开时间=原排头的离开时间+新排头办理业务的时间 et.NType = i; // 第i个队列的离开事件 OrderInsert(ev,et,com); // 将此离开事件et按升序插入事件表ev中 } } int main() { #ifdef FILE_TEST fq=fopen("a.txt","w"); // 打开a.txt文件,用于写入客户到达信息 fp=fopen("b.txt","w"); // 打开b.txt文件,用于写入有序事件表的历史记录 #endif printf("请输入银行营业时间长度(单位:分): "); scanf("%d",&CloseTime); srand(time(0)); // 设置随机数种子,以使每次运行程序产生的随机数不同(time(0)是长整型数,与调用时间有关) Bank_Simulation(); #ifdef FILE_TEST fclose(fq); // 关闭a.txt fclose(fp); // 关闭b.txt #endif }