我将代码分为三个部分来写,两个头文件一个是事件链表,一个是队列,和一个主函数
#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);