数据结构队列应用银行排队算法(离散事件模拟)

我将代码分为三个部分来写,两个头文件一个是事件链表,一个是队列,和一个主函数

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//定义事件链表的结点类型
typedef struct evnode {
	int occurTime;//事件发生时间
	int nType;//事件类型,-1代表到达事件,0-3代表四个窗口离开事件
	struct evnode* next;//指针域
} evNode, * evPtr;

//evPtr head;
//这是一个有头节点的链表
evPtr EventList(){ //初始化链表
	evPtr head;
	head = (evPtr)malloc(sizeof(evNode));
	if (head != NULL) {
		head->next = NULL;
		return head;
	}
	else {
		printf("Init error!\n");
	}
}


void DestoryEventList(evPtr head) { //销毁链表
	evPtr p = head->next;
	while (p != NULL) {
		free(head);
		head = p;
		p = p->next;
	}
	free(head);
}

int isEmpty(evPtr head) { //判断链表是否为空
	if (head->next == NULL || head == NULL)
		return 1;
	else
		return 0;
}

void addNode(evPtr head, int occurTime, int nType) { //添加结点,遍历,时间从小到大排列
	evPtr node, p, q;
	int temp;
	node = (evPtr)malloc(sizeof(evNode));
	node->next = NULL;
	node->occurTime = occurTime;
	node->nType = nType;
	if (head->next == NULL) {
		head->next = node;
	}
	else {
		p = head->next;
		q = head;
		while (p != NULL) {
			if (occurTime < p->occurTime) { //插入结点
				node->next = q->next;
				q->next = node;
				return;
			}
			else {
				q = p;
				p = p->next;
			}
		}//遍历完也没有插进去,就添加到末尾
		q->next = node;
	}
}

void deleteNode(evPtr head) { //在已经排列好顺序的链表里,只用删首任务就行
	evPtr Q;
	Q = head->next;
	head->next = Q->next;
	free(Q);
}

void display(evPtr head) {
	if (isEmpty(head) == 0) {
		evPtr Q;
		Q = head->next;
		while (Q != NULL) {
			if (Q->next == NULL)
				printf("(%d,%d)\n", Q->nType, Q->occurTime);
			else
				printf("(%d,%d) ", Q->nType, Q->occurTime);
			Q = Q->next;
		}
	}
	else {
		printf("链表为空!\n");
	}
}
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct client {
	int arrivalTime;//客户达到银行的时间
	int duration;//办理业务所需时间
} Client;

typedef Client QElemType;
//定义队列结点类型,即待执行指令
typedef struct QNode {
	QElemType data;
	struct QNode* next;
} QNode, * QueuePtr;

typedef struct {
	QueuePtr front;//头指针
	QueuePtr rear;//尾指针
} LinkQueue;

int InitQueue(LinkQueue *Q) {
	QueuePtr node;
	node = (QueuePtr)malloc(sizeof(QNode));
	if (node != NULL) {
		node->next = NULL; //创建头指针
		Q->front = Q->rear = node;
		return 1;
	}
	else
		return 0;
}

void DestroyQueue(LinkQueue* Q) {
	QueuePtr temp;
	while (Q->front != Q->rear) {
		temp = Q->front->next;
		free(Q->front);
		Q->front = temp;
	}
	free(Q->rear);
}

void ClearQueue(LinkQueue* Q) { //保留队列头指针和队列尾指针,其他结点销毁
	QueuePtr p, q;
	Q->rear = Q->front; //将尾指针归位到头指针
	Q->front->next = NULL;//天哪一定要记得把头节点next域设为NULL,不然还是被释放了的q,就乱指了
	q = Q->front->next; //队列头
	while (q != NULL) {
		p = q->next;
		free(q);
		q = p;
	}
}

int QueueEmpty(LinkQueue Q) { //是否为空,为空返回,非空返回0
	if (Q.front->next == NULL)
		return 1;
	else
		return 0;
}

int QueueLength(LinkQueue Q) { //返回队列长度
	QueuePtr p = Q.front->next;
	if (p == NULL) {//刚初始化完的队列长度为0
		return 0;
	}
	int count = 1;
	while (p != Q.rear) {
		count++;
		p = p->next;
	}
	return count;
}

QElemType GetHead(LinkQueue Q) { //若队列非空,返回队列首的值
	if (QueueEmpty(Q) != 1)
		return Q.front->next->data;
	else
		printf("队列为空!\n");
}

void EnQueue(LinkQueue* Q, QElemType e) { //将e元素插入队列
	QueuePtr node;
	node = (QueuePtr)malloc(sizeof(QNode));
	if (node != NULL) {
		node->next = NULL;
		node->data = e;
		Q->rear->next = node;
		Q->rear = node;
	}
	else
		printf("EnQueue error!\n");
}

QElemType DeQueue(LinkQueue* Q) { //删除队头元素,并返回其值
	QElemType e;
	QueuePtr q;
	if (QueueEmpty(*Q) != 1) {
		q = Q->front->next;
		e = Q->front->next->data;
		Q->front->next = q->next;
		if (Q->rear == q)//队头等于队尾,防止尾指针丢失
			Q->rear = Q->front;
		free(q);
		return e;
	}
	else
		printf("队列为空!\n");
}

void QueueTraverse(LinkQueue Q) { //从队列头到尾遍历队列元素并输出
	QueuePtr p = Q.front->next;
	//	printf("从队列头到尾遍历队列元素并输出:\n");
	if (QueueEmpty(Q) == 1) {
		printf("队列为空!\n");
		return;
	}
	while (p != NULL) {
		if (p != Q.rear)
			printf("(%d %d) ", p->data.arrivalTime, p->data.duration);
		else
			printf("(%d %d)\n", p->data.arrivalTime, p->data.duration);
		p = p->next;
	}
}

主函数部分是该算法的核心思想,头文件仅仅是两个数据结构模型的罗列(主函数会用到)。

#define _CRT_SECURE_NO_WARNINGS 1
#include "银行_链式队列.h"
#include "银行_事件链表.h"
#include <time.h>
#define CLOSE_TIME 40
int findMin(LinkQueue*);
double simulation() {
	double totalTime = 0;
	double customerNum = 0; //客户人数
	int durTime, interTime;
	//定义队列数组queue,下标0至3分别代表4个排队窗口队列
	LinkQueue queue[4];
	for (int i = 0; i < 4; i++)
		InitQueue(&queue[i]);//四个银行窗口初始化
	//建立事件链表对象,设定第一个客户到达的事件
	evPtr evlist=NULL;
	evlist = EventList();
	//初始化事件列表,加入第一个结点,起始时间为0,第一个事件为-1(新客户到达事件)
	evNode evItem;//evItem相当与一个临时变量
	evItem.nType = -1;
	evItem.occurTime = 0;
	evItem.next = NULL;
	addNode(evlist, 0, -1);
	evPtr head = evlist;
	int count = 1, count1 = 1;
	printf("初始化事件列表为:");
	display(head);
	//存储生成的随机数据方便查看
	//扫描并处理事件列表
	while (isEmpty(head) != 1) { //事件链表非空
		//删除事件链表中第一个结点存入evItem
		printf("------------------第%d次循环------------------\n", count1++);
		evlist = head->next;
		evItem.nType = evlist->nType;//-1
		evItem.occurTime = evlist->occurTime;//0
		printf("显示当前事件表:");
		display(head);
		printf("删除事件链表中第一个结点并存入evItem,值为[%d,%d]\n", evItem.nType, evItem.occurTime);
		deleteNode(head);
		//总共只有两大类事件,客户到达和客户离开
		if (evItem.nType == -1) {
			/* --是新客户到达事件--  */
			// 客户人数加1
			customerNum++;
			// 输入随机数durTime 和interTime,(durTime代表当前客户服务时间,
			// interTimed代表下一个客户到来的间隔时间)
			printf("是新客户到达事件,请输入durTime,interTime:");
			scanf("%d %d", &durTime, &interTime);
			printf("第%d组随机数据为:(服务时间:%d,间隔时间:%d)\n", count++, durTime, interTime);
			if (evItem.occurTime + interTime < CLOSE_TIME) { //下一个客户来时银行还没关门
				//而已经来的顾客无论需要处理多久都会把任务处理完
				//设定下一位客户到达的事件插入事件表
				evItem.next = NULL;
				printf("把下一位客户到达的事件插入事件表后的新表:");
				addNode(head, evItem.occurTime+interTime, -1);
				display(head);//后面入队列时还要用,就把值还原回去
			}
			//寻找最短队列
			int di;
			di = findMin(queue);
			printf("新客户进入%d号窗口\n", di + 1);
			//将当前正在处理的到来事件放入最短排队窗口队列
			Client current;
			current.arrivalTime= evItem.occurTime;
			current.duration = durTime;
			EnQueue(&queue[di], current);
			//如果当前客户到达的窗口没有人在排队(他是第一个客户)
			//就可以将本客户离开的事件进行预测,并加入事件链表
			if (QueueLength(queue[di]) == 1) {
				printf("本客户是%d号窗口的队首客户,添加离开事件\n", di + 1);
				addNode(head, current.arrivalTime +current.duration, di);
				printf("把队首客户离开的事件插入事件表后的新表:");
				display(head);
			}
		}
		else {
			/* --是客户离开事件-- */
			//获得客户所在的窗口号(队列号)
			//服务总时间累加
			Client del;
			del = DeQueue(&queue[evItem.nType]);
			totalTime += del.duration;
			printf("是老客户离开事件,将要离开的客户是%d窗口的(%d,%d)\n", evItem.nType + 1, del.arrivalTime, del.duration);
			//如果还有人在排队,则把队头客户的离开事件插入事件表
			if (QueueLength(queue[evItem.nType]) != 0) { //相当于后面排队的人向前走一步
				printf("还有人在排队,往前走一步并添加离开事件\n");
				evItem.next = NULL;
				Client d = GetHead(queue[evItem.nType]);	
				printf("%d ********************%d",del.arrivalTime,d.arrivalTime);
				addNode(head, evItem.occurTime+d.duration, evItem.nType);
				printf("把队首客户离开的事件插入事件表后的新表***********************:");
				display(head);
			}
		}
		printf("四个窗口的排队现状:\n");
		for (int i = 0; i < 4; i++) {
			printf("%d号窗口:", i + 1);
			QueueTraverse(queue[i]);
		}
	}
	return totalTime / customerNum;

}

int findMin(LinkQueue*queue) {
	int minlen = QueueLength(queue[0]);
	int di = 0; //保存最短队伍窗口编号
	for (int i = 1; i < 4; i++) {
		if (QueueLength(queue[i]) < minlen) {
			di = i;
			minlen = QueueLength(queue[i]);
		}
	}
	return di;
}
int main() {
	printf("银行排队算法测试:\n");
	double percent;
	percent = simulation();
	printf("通过计算本次离散随机事件,得银行处理个人业务的平均时间为:%0.4f分钟\n", percent);
}

将事件分为到达事件和离开事件,网上有很多个版本的代码,但实际上有一个细节很多人都是错的(虽然最终答案一样),就是在主函数的else部分,当删除了队头之后,如果后面还有队伍需要将下一个队列元素的离开事件插入,关键点就是这个元素的离开时间,应该是当前evItem的发生时间加上该队列元素的办理时间之和。因为删除该队头后立马就会执行其队头下一个元素的业务办理。addNode(head, evItem.occurTime+d.duration, evItem.nType);

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值