数据结构学习笔记 --- 队列的应用举例(离散事件模拟)

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
}


你可能感兴趣的:(数据结构,struct,File,null,Random,FP)