【实验目的】
- 了解离散事件的模拟机制(模拟银行系统);
- 掌握队列的基本操作,加深对队列数据结构的理解;
- 加强抽象数据类型的能力。
【实验原理】
- 创建队列类的定义,时间类和银行类的定义。
- 队列的每个元素为一个客户,每个客户有一个客户编号val,采用链式队列存储,可进行出队、入队、判断队空、返回队长等基本操作。
- 使用时间类表示时间,精确到分钟,可通过运算符重载实现基本运算。
- 使用银行类模拟银行的运转,包含开门关门时间和4个窗口(队列)。操作包括:初始化OpenForDay, 事件驱动EventDrived(),下班处理CloseForDay()
【需求分析】
问题描述
假设某银行有四个窗口对外接待客户,从早晨银行开门起不断有客户进入银行。由于每个窗口在某个时刻只能接待一个客户,因此在客户人数众多时需在每个窗口前顺次排队,对于刚进入银行的客户,如果某个窗口的业务员正空闲,则可上前办理业务,反之,若四个窗口均有客户所占,他便会排在人数最少的队伍后面。现在需要编制程序以模拟银行的这种业务活动。基本要求
(1)初始化(OpenForDay),模拟银行开门时各数据结构的状态。
(2)事件驱动(EventDrived), 对客户到达和离开事件做相应处理。
(3)下班处理(CloseForDay),模拟银行关门的动作,统计客户平均逗留时间。
【概要设计】
数据结构:
(1)队列Queue的每个元素Customer为一个客户,每个客户有一个客户编号val,采用链式队列存储,可进行出队、入队、判断队空、返回队长等基本操作。
(2)使用时间类Time表示时间,数据成员包括小时hour和分钟minute,默认时间初始化为0:0,可手动输入时间,可通过运算符重载实现时间的>、<、==、+、-、/等运算。
(3)使用银行类Bank模拟银行的运转,数据成员包括:开门关门时间Open, Close;进入银行的客户总数AllNum,完成业务办理的客户总数TrueNum;每位客户进门时间InTime和离开时间OutTime;以及4个窗口(队列)。数据操作包括:初始化OpenForDay, 事件驱动EventDrived(),下班处理CloseForDay(),发现最短队列GetShort(),显示窗口排队状况ShowWindows()程序模块:
(1)基本定义模块;
(2)队列操作模块;
(3)时间操作模块;
(4)银行操作模块
①初始化OpenForDay()
②事件驱动EventDrived()
③下班处理CloseForDay()各模块之间调用关系及算法设计:
基本定义模块包括队列、时间、银行类的定义;队列、时间、银行模块包括其类的基本操作,银行类是队列类、时间类的友元类,银行的操作包含了队列操作模块中的出入队、时间操作模块中时间运算。其他
规定每分钟20%的概率来一位客户,一位客户有10%的概率在窗口办理时间超过1小时
【程序清单】
BasicStruct.h
#pragma once
#include <iostream>
#define OK 0
#define ERR -1
#define MAXSIZE 400
using namespace std;
typedef int status;
//顾客定义
typedef struct Customer
{
int val; //顾客编号
struct Customer *next; //后继指针
}Node;
//链式队列定义
class Queue
{
public:
void InitQueue(); //初始化队列
bool IsEmpty(); //判断队列是否为空
void EnQueue(int val);//入队
int GetHead(); //取队头元素
void DeQueue(); //出队
int GetLength();//求队列长度
friend class Bank;
private:
Node *front; //队头指针
Node *rear; //队尾指针
};
//时间的定义
class Time
{
public:
Time() :hour(0), minute(0) {}
Time(int h, int m) :hour(h), minute(m) {}
Time(const Time &b);
void SetTime(); //设置时间
void display(); //读取时间
bool operator> (Time &time2); //时间比较运算>
bool operator< (Time &time2); //时间比较运算<
bool operator== (Time &time2); //时间比较运算==
Time operator+ (const Time &time2); //时间相加+
Time operator- (const Time &time2); //时间相减-
Time operator/ (int x); //时间相除/
Time& operator++ (); //时间自增一分钟(前置)
friend class Bank;
private:
int hour, minute;
};
//银行的定义
class Bank
{
public:
Queue* Windows = new Queue[4];//四个服务窗口
Time Open, Close; //开门关门时间
status OpenForDay(); //初始化(OpenForDay),模拟银行开门时各数据结构的状态。
status EventDrived(Time Now);//事件驱动(EventDrived), 对客户到达和离开事件做相应处理。
status CloseForDay(); //下班处理(CloseForDay),模拟银行关门时的动作,统计客户平均逗留时间。
int GetShort(); //发现最短队列
void ShowWindows(); //显示窗口排队状况
private:
int AllNum; //进入银行的客户总数
int TrueNum; //完成业务办理的客户总数
Time* InTime = new Time[MAXSIZE];//每位客户进门时间.
Time* OutTime = new Time[MAXSIZE];//每位客户离开时间.
};
QueueOperation.cpp
#include "BasicStruct.h"
//初始化队列
void Queue::InitQueue()
{
front = rear = new Node;
if (!front)
exit(OVERFLOW);
front->next = NULL;
}
//判断队列是否为空
bool Queue::IsEmpty()
{
return (front == rear);
}
//入队
void Queue::EnQueue(int val)
{
Node *newNode = new Node;
newNode->val = val;
newNode->next = NULL;
if (newNode == NULL)
return;
else {
rear->next = newNode;
rear = rear->next;
}
}
//取队头元素
int Queue::GetHead()
{
if (front != rear)
return front->next->val;
else
{
return ERR;
}
}
//出队
void Queue::DeQueue()
{
if (IsEmpty())
return;
else
{
int val = front->next->val;
Node *deNode = front->next;
front->next = deNode->next;
if (rear == deNode) //only one element
rear = front;
delete deNode;
}
}
//求队列长度
int Queue::GetLength()
{
int i = 0;
Node *p;
p = front;
while (rear != p)
{
p = p->next;
i++;
}
return i;
}
TimeOperation.cpp
#include "BasicStruct.h"
//拷贝构造函数
Time::Time(const Time &b)
{
this->hour = b.hour;
this->minute = b.minute;
}
//设置时间
void Time::SetTime()
{
int h, m;
cin >> h >> m;
if (h >= 0 && m >= 0) { //判断输入值合法性
hour = h, minute = m;
}
else
return;
}
//读取时间
void Time::display()
{
cout << " " << hour << ": " << minute;
}
//时间比较运算>
bool Time::operator> (Time &time2)
{
if (hour > time2.hour) { return true; }
else if (hour == time2.hour && minute > time2.minute) { return true; }
else { return false; }
}
//时间比较运算<
bool Time::operator< (Time &time2)
{
if (hour < time2.hour) { return true; }
else if (hour == time2.hour && minute < time2.minute) { return true; }
else { return false; }
}
//时间比较运算==
bool Time::operator== (Time &time2)
{
if (hour == time2.hour && minute == time2.minute) { return true; }
else { return false; }
}
//时间相加+
Time Time::operator+ (const Time &time2)
{
hour = hour + time2.hour;
minute = minute + time2.minute;
//加法,分、秒满60进位
if (minute >= 60) {
hour++;
minute = minute % 60;
}
return Time(hour, minute);
}
//时间相减-
Time Time::operator- (const Time &time2)
{
hour = hour - time2.hour;
minute = minute - time2.minute;
//减法借位
if (hour > 0 && minute < 0) {
hour--; minute += 60;
}
else if (hour < 0 && minute > 0) {
hour--; minute -= 60;
}
return Time(hour, minute);
}
//时间相除
Time Time::operator/ (int x)
{
if (x != 0) {
int t = (hour * 60 + minute) / x;
hour = -(int)(t / 60);
minute = t % 60;
return Time(hour, minute);
}
else {
exit;
}
}
//时间自增一分钟(前置)
Time & Time::operator++ ()
{
minute++;
if (minute == 60) {
hour++; minute = 0;
}
return *this;
}
BankOperation.cpp
#include "BasicStruct.h"
#include <time.h>
#define random(x) rand()%x
//初始化(OpenForDay),模拟银行开门时各数据结构的状态。
status Bank::OpenForDay()
{
Open.SetTime(); //设置开门时间
Close.SetTime();//设置关门时间
Windows[0].InitQueue();//初始化各窗口
Windows[1].InitQueue();
Windows[2].InitQueue();
Windows[3].InitQueue();
AllNum = 0;
TrueNum = 0;
return OK;
}
//返回最短队列编号
int Bank::GetShort()
{ //有4个窗口可选择
int choice[4] = { Windows[0].GetLength(), Windows[1].GetLength(), Windows[2].GetLength(), Windows[3].GetLength() };
int len[4] = { Windows[0].GetLength(), Windows[1].GetLength(), Windows[2].GetLength(), Windows[3].GetLength() };
//使用冒泡排序求得最短的队列
int i, j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3 - i; j++)
{
if (len[j] > len[j + 1])
swap(len[j], len[j + 1]);
}
}
for (i = 0; i < 4; i++)
{
if (len[0] == choice[i])
return i;
}
}
//事件驱动(EventDrived), 对客户到达和离开事件做相应处理。
status Bank::EventDrived(Time Now) {
//srand((unsigned)time(NULL));
int come = random(5);
if (come == 1) {//每分钟有20%的几率来一位顾客
InTime[AllNum] = Now; //记录顾客进门时间
int i = 0;
//顾客前往人数最少的窗口,更新窗口服务时间
while (i < 4) {
if (i != GetShort()) {
i++;
}
else {// 这里设置每位顾客有10%的几率办理时长超过1小时
if (!Windows[i].IsEmpty()) {
int pre = (Windows[i].rear->val);
Time t(random(10) == 1 ? 1 : 0, random(60));
OutTime[AllNum] = OutTime[pre - 1] + t;
break;
}
else {
Time t(random(10) == 1 ? 1 : 0, random(60));
OutTime[AllNum] = t + Now;
Windows[i].EnQueue(AllNum);
break;
}
}
}
AllNum++;
}//if
for (int i = 0; i < 4; i++) {
Customer* p = Windows[i].front;
for (int j = 0; j < Windows[i].GetLength(); j++) {
if (p->next == NULL)
break;
p = p->next;
if (Now == OutTime[p->val]) {
Windows[i].DeQueue();// 服务完的客户从队列中删除
TrueNum++;// 已服务人数加一
}
}
}
return OK;
}
//下班处理(CloseForDay),模拟银行关门时的动作,统计客户平均逗留时间。
status Bank::CloseForDay()
{ //计算并显示客户平均逗留时间
Time AveTime(0, 0);
for (int j = 0; j < AllNum; j++) {
if (OutTime[j] > Close) {
continue; //只统计完成业务办理的客户,仍在排队的不计,队伍解散
}
else {
AveTime = AveTime + OutTime[j] - InTime[j];//出门时间减去进门时间
if (j < 10) cout << " " << 1 + j; //打印客户编号
else cout << 1 + j;
InTime[j].display(); //打印客户进门时间
OutTime[j].display(); //打印客户出门时间
(OutTime[j] - InTime[j]).display();//打印客户逗留时间
cout << endl;
}
}
AveTime = AveTime / TrueNum;
cout << "\n完成业务办理的客户总数为:" << TrueNum << endl;//打印完成业务办理的客户数
cout << "人均逗留时间为"; //打印客户平均逗留时间
AveTime.display();
cout << endl;
//清空所有队列,请未完成业务办理的客户改日再来
for (int i = 0; i < 4; i++)
{
while (!Windows[i].IsEmpty())
{
Windows[i].DeQueue();
}
}
return OK;
}
//显示窗口状况
void Bank::ShowWindows()
{ //依次打印各个窗口前的顾客
for (int i = 0; i < 4; i++) {
Customer* p = Windows[i].front;
cout << i + 1 << "号窗口 ";
while (p != Windows[i].rear)
{
p = p->next;
cout << " " << p->val + 1 << " ";
}
cout << endl;
}
}
Main.cpp
#include <Windows.h>
#include "BasicStruct.h"
int main() {
Bank ABC;
cout << "设置银行上班时间和下班时间:" << endl;
ABC.OpenForDay();//初始化
Time now(ABC.Open);
//模拟顾客排队和离队
while (true)
{
system("cls");
cout << "----------------------------------------" << endl;
now.display();
cout << "\n----------------------------------------" << endl;
cout << endl;
ABC.ShowWindows();//显示窗口状况
ABC.EventDrived(now); //事件驱动
++now;
//system("pause");
if (now == ABC.Close)
break;
}
cout << endl;
cout << "----------------------------------------" << endl;
cout << "客户编号 " << "进门时间 " << "出门时间 " << "停留时间 " << endl;;
ABC.CloseForDay();//下班
system("pause");
return 0;
}
运行结果
输入上班和下班时间,这里假设9点上班,12点下班