/* algo3-12.c 银行业务模拟。实现算法3.6、3.7的程序 */

/* c1.h (程序名) */
#include <string.h>
#include <ctype.h>
#include <malloc.h> /* malloc()等 */
#include <limits.h> /* INT_MAX等 */
#include <stdio.h> /* EOF(=^Z或F6),NULL */
#include <stdlib.h> /* atoi() */
#include <io.h> /* eof() */
#include <math.h> /* floor(),ceil(),abs() */
#include <process.h> /* exit() */

/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1

/* #define OVERFLOW -2 因为在math.h中已定义OVERFLOW的值为3,故去掉此行 */
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */

//////////////////////////////线性链表操作/////////////////////////////////////////
 typedef struct /* 定义ElemType为结构体类型 */
 {
   int OccurTime; /* 事件发生时刻 */
   int NType; /* 事件类型,num_windows表示到达事件,0至num_windows-1表示num_windows个窗口的离开事件 */
 }event,ElemType; /* 事件类型,有序链表LinkList的数据元素类型 */

 /* c2-5.h 带头结点的线性链表类型 */
 typedef struct LNode /* 结点类型 */
 {
   ElemType data;
   struct LNode *next;
 }LNode,*Link,*Position;

 typedef struct LinkList /* 链表类型 */
 {
   Link head,tail; /* 分别指向线性链表中的头结点和最后一个结点 */
   int len; /* 指示线性链表中数据元素的个数 */
 }LinkList;
 typedef LinkList eventList; /* 事件链表类型,定义为有序链表 */

 /* bo2-6.c 具有实用意义的线性链表(存储结构由c2-5.h定义)的24个基本操作 */
 Status MakeNode(Link *p,ElemType e)
 { /* 分配由p指向的值为e的结点,并返回OK;若分配失败。则返回ERROR */
   *p=(Link)malloc(sizeof(LNode));
   if(!*p)
     return ERROR;
   (*p)->data=e;
   return OK;
 }

 void FreeNode(Link *p)
 { /* 释放p所指结点 */
   free(*p);
   *p=NULL;
 }

 Status InitList(LinkList *L)
 { /* 构造一个空的线性链表 */
   Link p;
   p=(Link)malloc(sizeof(LNode)); /* 生成头结点 */
   if(p)
   {
     p->next=NULL;
     (*L).head=(*L).tail=p;
     (*L).len=0;
     return OK;
   }
   else
     return ERROR;
 }

 Status ClearList(LinkList *L)
 { /* 将线性链表L重置为空表,并释放原链表的结点空间 */
   Link p,q;
   if((*L).head!=(*L).tail)/* 不是空表 */
   {
     p=q=(*L).head->next;
     (*L).head->next=NULL;
     while(p!=(*L).tail)
     {
       p=q->next;
       free(q);
       q=p;
     }
     free(q);
     (*L).tail=(*L).head;
     (*L).len=0;
   }
   return OK;
 }

 Status DestroyList(LinkList *L)
 { /* 销毁线性链表L,L不再存在 */
   ClearList(L); /* 清空链表 */
   FreeNode(&(*L).head);
   (*L).tail=NULL;
   (*L).len=0;
   return OK;
 }

 Status InsFirst(LinkList *L,Link h,Link s) /* 形参增加L,因为需修改L */
 { /* h指向L的一个结点,把h当做头结点,将s所指结点插入在第一个结点之前 */
   s->next=h->next;
   h->next=s;
   if(h==(*L).tail) /* h指向尾结点 */
     (*L).tail=h->next; /* 修改尾指针 */
   (*L).len++;
   return OK;
 }

 Status DelFirst(LinkList *L,Link h,Link *q) /* 形参增加L,因为需修改L */
 { /* h指向L的一个结点,把h当做头结点,删除链表中的第一个结点并以q返回。 */
   /* 若链表为空(h指向尾结点),q=NULL,返回FALSE */
   *q=h->next;
   if(*q) /* 链表非空 */
   {
     h->next=(*q)->next;
     if(!h->next) /* 删除尾结点 */
       (*L).tail=h; /* 修改尾指针 */
     (*L).len--;
     return OK;
   }
   else
     return FALSE; /* 链表空 */
 }

 Status Append(LinkList *L,Link s)
 { /* 将指针s(s->data为第一个数据元素)所指(彼此以指针相链,以NULL结尾)的 */
   /* 一串结点链接在线性链表L的最后一个结点之后,并改变链表L的尾指针指向新 */
   /* 的尾结点 */
   int i=1;
   (*L).tail->next=s;
   while(s->next)
   {
     s=s->next;
     i++;
   }
   (*L).tail=s;
   (*L).len+=i;
   return OK;
 }

 Position PriorPos(LinkList L,Link p)
 { /* 已知p指向线性链表L中的一个结点,返回p所指结点的直接前驱的位置 */
   /* 若无前驱,则返回NULL */
   Link q;
   q=L.head->next;
   if(q==p) /* 无前驱 */
     return NULL;
   else
   {
     while(q->next!=p) /* q不是p的直接前驱 */
       q=q->next;
     return q;
   }
 }

 Status Remove(LinkList *L,Link *q)
 { /* 删除线性链表L中的尾结点并以q返回,改变链表L的尾指针指向新的尾结点 */
   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;
 }

 Status InsBefore(LinkList *L,Link *p,Link s)
 { /* 已知p指向线性链表L中的一个结点,将s所指结点插入在p所指结点之前, */
   /* 并修改指针p指向新插入的结点 */
   Link q;
   q=PriorPos(*L,*p); /* q是p的前驱 */
   if(!q) /* p无前驱 */
     q=(*L).head;
   s->next=*p;
   q->next=s;
   *p=s;
   (*L).len++;
   return OK;
 }

 Status InsAfter(LinkList *L,Link *p,Link s)
 { /* 已知p指向线性链表L中的一个结点,将s所指结点插入在p所指结点之后, */
   /* 并修改指针p指向新插入的结点 */
   if(*p==(*L).tail) /* 修改尾指针 */
     (*L).tail=s;
   s->next=(*p)->next;
   (*p)->next=s;
   *p=s;
   (*L).len++;
   return OK;
 }

 Status SetCurElem(Link p,ElemType e)
 { /* 已知p指向线性链表中的一个结点,用e更新p所指结点中数据元素的值 */
   p->data=e;
   return OK;
 }

 ElemType GetCurElem(Link p)
 { /* 已知p指向线性链表中的一个结点,返回p所指结点中数据元素的值 */
   return p->data;
 }

 Status ListEmpty(LinkList L)
 { /* 若线性链表L为空表,则返回TRUE,否则返回FALSE */
   if(L.len)
     return FALSE;
   else
     return TRUE;
 }

 int ListLength(LinkList L)
 { /* 返回线性链表L中元素个数 */
   return L.len;
 }

 Position GetHead(LinkList L)
 { /* 返回线性链表L中头结点的位置 */
   return L.head;
 }

 Position GetLast(LinkList L)
 { /* 返回线性链表L中最后一个结点的位置 */
   return L.tail;
 }

 Position NextPos(Link p)
 { /* 已知p指向线性链表L中的一个结点,返回p所指结点的直接后继的位置 */
   /* 若无后继,则返回NULL */
   return p->next;
 }

 Status LocatePos(LinkList L,int i,Link *p)
 { /* 返回p指示线性链表L中第i个结点的位置,并返回OK,i值不合法时返回ERROR */
   /* i=0为头结点 */
   int j;
   if(i<0||i>L.len)
     return ERROR;
   else
   {
     *p=L.head;
     for(j=1;j<=i;j++)
       *p=(*p)->next;
     return OK;
   }
 }

 Position LocateElem(LinkList L,ElemType e,Status (*compare)(ElemType,ElemType))
 { /* 返回线性链表L中第1个与e满足函数compare()判定关系的元素的位置, */
   /* 若不存在这样的元素,则返回NULL */
   Link p=L.head;
   do
     p=p->next;
   while(p&&!(compare(p->data,e))); /* 没到表尾且没找到满足关系的元素 */
   return p;
 }

 Status ListTraverse(LinkList L,Status(*Lvisit)(ElemType))
 { /* 依次对L的每个数据元素调用函数visit()。一旦visit()失败,则操作失败 */
   Link p=L.head->next;
   int j;
   for(j=1;j<=L.len;j++)
   {
     Lvisit(p->data);
     p=p->next;
   }
   printf("\n");
   return OK;
 }

 Status Lvisit(ElemType lc)
 {
	printf("ElemType lc.OccurTime %d, lc.NType %d\n",lc.OccurTime, lc.NType);
	return OK;
 }

 Status OrderInsert(LinkList *L,ElemType e,int (*comp)(ElemType,ElemType))
 { /* 已知L为有序线性链表,将元素e按非降序插入在L中。(用于一元多项式) */
   Link o,p,q;
   q=(*L).head;
   p=q->next;
   while(p!=NULL&&comp(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; /* 修改尾结点 */
   return OK;
 }

 Status LocateElemP(LinkList L,ElemType e,Position *q,int(*compare)(ElemType,ElemType))
 { /* 若升序链表L中存在与e满足判定函数compare()取值为0的元素,则q指示L中 */
   /* 第一个值为e的结点的位置,并返回TRUE;否则q指示第一个与e满足判定函数 */
   /* compare()取值>0的元素的前驱的位置。并返回FALSE。(用于一元多项式) */
   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;
   }
 }


 /////////////////////////////////////队列操作/////////////////////////////////////////
 typedef struct QElemType
 {
   int ArrivalTime; /* 到达时刻 */
   int Duration; /* 办理事务所需时间 */
 }QElemType; /* 定义QElemType(队列的数据元素类型)为结构体类型; */

 /* c3-2.h 单链队列--队列的链式存储结构 */
 typedef struct QNode
 {
   QElemType data;
   struct QNode *next;
 }QNode,*QueuePtr;

 typedef struct LinkQueue
 {
   QueuePtr front,rear; /* 队头、队尾指针 */
 }LinkQueue;
 /* bo3-2.c 链队列(存储结构由c3-2.h定义)的基本操作(9个) */
 Status InitQueue(LinkQueue *Q)
 { /* 构造一个空队列Q */
   (*Q).front=(*Q).rear=(QueuePtr)malloc(sizeof(QNode));
   if(!(*Q).front)
     exit(OVERFLOW);
   (*Q).front->next=NULL;
   return OK;
 }

 Status Destroynum_windowseue(LinkQueue *Q)
 { /* 销毁队列Q(无论空否均可) */
   while((*Q).front)
   {
     (*Q).rear=(*Q).front->next;
     free((*Q).front);
     (*Q).front=(*Q).rear;
   }
   return OK;
 }

 Status ClearQueue(LinkQueue *Q)
 { /* 将Q清为空队列 */
   QueuePtr p,q;
   (*Q).rear=(*Q).front;
   p=(*Q).front->next;
   (*Q).front->next=NULL;
   while(p)
   {
     q=p;
     p=p->next;
     free(q);
   }
   return OK;
 }

 Status QueueEmpty(LinkQueue Q)
 { /* 若Q为空队列,则返回TRUE,否则返回FALSE */
   if(Q.front==Q.rear)
     return TRUE;
   else
     return FALSE;
 }

 int QueueLength(LinkQueue Q)
 { /* 求队列的长度 */
   int i=0;
   QueuePtr p;
   p=Q.front;
   while(Q.rear!=p)
   {
     i++;
     p=p->next;
   }
   return i;
 }

 Status GetHead_Q(LinkQueue Q,QElemType *e) /* 避免与bo2-6.c重名 */
 { /* 若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR */
   QueuePtr p;
   if(Q.front==Q.rear)
     return ERROR;
   p=Q.front->next;
   *e=p->data;
   return OK;
 }

 Status EnQueue(LinkQueue *Q,QElemType e)
 { /* 插入元素e为Q的新的队尾元素 */
   QueuePtr p=(QueuePtr)malloc(sizeof(QNode));
   if(!p) /* 存储分配失败 */
     exit(OVERFLOW);
   p->data=e;
   p->next=NULL;
   (*Q).rear->next=p;
   (*Q).rear=p;
   return OK;
 }

 Status DeQueue(LinkQueue *Q,QElemType *e)
 { /* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */
   QueuePtr p;
   if((*Q).front==(*Q).rear)
     return ERROR;
   p=(*Q).front->next;
   *e=p->data;
   (*Q).front->next=p->next;
   if((*Q).rear==p)
     (*Q).rear=(*Q).front;
   free(p);
   return OK;
 }

 Status QueueTraverse(LinkQueue Q, Status(*Qvisit)(QElemType))
 { /* 从队头到队尾依次对队列Q中每个元素调用函数vi()。一旦vi失败,则操作失败 */
   QueuePtr p;
   p=Q.front->next;
   while(p)
   {
     Qvisit(p->data);
     p=p->next;
   }
   printf("\n");
   return OK;
 }

 Status Qvisit(QElemType c)
  {
    printf("QElemType c.ArrivalTime %d, c.Duration %d\n",c.ArrivalTime, c.Duration);
    return OK;
  }

///////////////////////////////////主程序///////////////////////////////////////////
 /* 程序中用到的主要变量(全局)。算法3.7 */
 #define num_windows 4 /* 客户队列数 */
 #define max_interval 5 /* 两相邻到达的客户的时间间隔最大值 */
 #define max_time 30 /* 每个客户办理业务的时间最大值 */

 eventList el; /* 事件表 */
 event en; /* 事件 */
 event et; /* 临时变量 */
 LinkQueue q[num_windows]; /* num_windows个客户队列 */
 QElemType customer; /* 客户记录 */
 int TotalTime=0,CustomerNum=0; /* 累计客户逗留时间,客户数(初值为0) */
 int CloseTime; /* 银行营业时间(单位是分) */

 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);
 }

 void OpenForDay()
 { /* 初始化操作 */
   int i;
   InitList(&el); /* 初始化事件链表为空 */
   en.OccurTime=0; /* 设定第一个客户到达事件 */
   en.NType=num_windows; /* 到达 */
   OrderInsert(&el,en,cmp); /* 插入事件表 */
   for(i=0;i<num_windows;++i) /* 置空队列 */
     InitQueue(&q[i]);
 }

 void Random(int *d,int *i)
 {
   *d=rand()%max_time+1; /* 1到max_time之间的随机数 */
   *i=rand()%max_interval+1; /* 1到max_interval之间的随机数 */
 }

 int Minimum(LinkQueue Q[]) /* 返回最短队列的序号 */
 {
   int l[num_windows];
   int i,k;
   for(i=0;i<num_windows;i++)
     l[i]=QueueLength(Q[i]);
   k=0;
   for(i=1;i<num_windows;i++)
     if(l[i]<l[0])
     {
       l[0]=l[i];
       k=i;
     }
   return k;
 }

 void CustomerArrived()
 { /* 处理客户到达事件,en.NType=num_windows */
   QElemType f;
   int durtime,intertime,i;
   ++CustomerNum;
   Random(&durtime,&intertime); /* 生成随机数 */
   et.OccurTime=en.OccurTime+intertime; /* 下一客户到达时刻 */
   et.NType=num_windows; /* 队列中只有一个客户到达事件 */
   if(et.OccurTime<CloseTime) /* 银行尚未关门,插入事件表 */
     OrderInsert(&el,et,cmp);
   i=Minimum(q); /* 求长度最短队列的序号,等长为最小的序号 */
   f.ArrivalTime=en.OccurTime;
   f.Duration=durtime;
   EnQueue(&q[i],f);
   if(QueueLength(q[i])==1)
   {
     et.OccurTime=en.OccurTime+durtime;
     et.NType=i;
     OrderInsert(&el,et,cmp); /* 设定第i队列的一个离开事件并插入事件表 */
   }
 }

 void CustomerDeparture()
 { /* 处理客户离开事件,en.NType<num_windows */
   int i;
   i=en.NType;
   DeQueue(&q[i],&customer); /* 删除第i队列的排头客户 */
   TotalTime+=en.OccurTime-customer.ArrivalTime; /* 累计客户逗留时间 */
   if(!QueueEmpty(q[i]))
   { /* 设定第i队列的一个离开事件并插入事件表 */
     GetHead_Q(q[i],&customer);
     et.OccurTime=en.OccurTime+customer.Duration;
     et.NType=i;
     OrderInsert(&el,et,cmp);
   }
 }

 void Bank_Simulation()
 {
   Link p;
   int i;
   OpenForDay(); /* 初始化 */
   while(!ListEmpty(el) || en.OccurTime < CloseTime)
   {
     DelFirst(&el,GetHead(el),&p);
     en.OccurTime=GetCurElem(p).OccurTime;
     en.NType=GetCurElem(p).NType;
     if(en.NType==num_windows && en.OccurTime < CloseTime)
       CustomerArrived(); /* 处理客户到达事件 */
     else
       CustomerDeparture(); /* 处理客户离开事件 */

     printf("en.OccurTime %d\n", en.OccurTime);
     printf("print the event list:\n");
     ListTraverse(el,Lvisit);

     printf("print the queue[num_windows]:\n");
     for(i=0;i<num_windows;i++){
    	 printf("q[%d]:\n", i);
    	 QueueTraverse(q[i],Qvisit);
     }

   } /* 计算并输出平均逗留时间 */
   printf("顾客总数:%d, 所有顾客共耗时:%d分钟, 平均每人耗时: %d分钟\n",CustomerNum,TotalTime,TotalTime/CustomerNum);
 }

int
main(int argc, char *argv[])
 {
   printf("请输入银行营业时间长度(单位:分)\n");
   scanf("%d",&CloseTime);
   Bank_Simulation();

   return 0;
 }