|
|
|
|
# 银行业务模拟系统
|
|
|
|
|
|
|
|
|
|
成员:费良荣、冯国平、武杭凯、李聪颖
|
|
|
|
|
|
|
|
|
|
**摘要**:本项目针对银行业务系统运行的问题,实现客户和窗口的功能、管理客户、队列的功能和计算一天中客户在银行逗留的平均时间的功能。为了有效地存储和处理银行的开门时间、关门时间、营业窗口数目、银行营业的工作记录、客户的到达时间、离开时间等数据,采用了线性表、队列等数据结构和排序、随机数等数据结构知识。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
项目开发过程中采用 Kanban(看板)进行任务管理和分工协作,并使用 Git 对程序代码和文档进行版本管理。任务分工情况如下:
|
|
|
|
|
|
|
|
|
|
| 任务 | 设计 | 开发 | 测试 |
|
|
|
|
|
| ---- | ---- | ---- | ---- |
|
|
|
|
|
| C1 银行业务模拟,统计一天内客户在银行逗留的平均时间 | 费良荣 | 冯国平 | 武杭凯 |
|
|
|
|
|
| C2 比较事件发生先后 | 冯国平 | 李聪颖 | 费良荣 |
|
|
|
|
|
| C3 银行开门 | 李聪颖 | 武杭凯 | 冯国平 |
|
|
|
|
|
| C4 插入事件 | 武杭凯 | 冯国平 | 李聪颖 |
|
|
|
|
|
| C5 客户进门 | 费良荣 | 费良荣 | 李聪颖 |
|
|
|
|
|
| C6 客户离开 | 冯国平 | 武杭凯 | 费良荣 |
|
|
|
|
|
| C7 求长度最短队列 | 李聪颖 | 冯国平 | 武杭凯 |
|
|
|
|
|
| C8 链表初始化 | 费良荣 | 费良荣 | 冯国平 |
|
|
|
|
|
| C9 在第i个位置之前插入元素e | 武杭凯 | 李聪颖 | 费良荣 |
|
|
|
|
|
| C10 判断链表是否为空 | 冯国平 | 武杭凯 | 李聪颖 |
|
|
|
|
|
| C11 删除链表中第一个结点并以q返回 | 费良荣 | 李聪颖 | 冯国平 |
|
|
|
|
|
| C12 返回链表头结点 | 武杭凯 | 费良荣 | 费良荣 |
|
|
|
|
|
| C13 已知p指向线性链表中的一个结点,返回p所指结点中元素的值 | 费良荣 | 冯国平 | 李聪颖 |
|
|
|
|
|
| C14 打印事件链表 | 武杭凯 | 费良荣 | 冯国平 |
|
|
|
|
|
| C15 遍历链表 | 李聪颖 | 武杭凯 | 费良荣 |
|
|
|
|
|
| C16 链队列的初始化 | 费良荣 | 冯国平 | 李聪颖 |
|
|
|
|
|
| C17 入队 | 费良荣 | 武杭凯 | 冯国平 |
|
|
|
|
|
| C18 出队 | 李聪颖 | 费良荣 | 武杭凯 |
|
|
|
|
|
| C19 返回队列的长度 | 冯国平 | 李聪颖 | 武杭凯 |
|
|
|
|
|
| C20 获取队头元素 | 冯国平 | 武杭凯 | 李聪颖 |
|
|
|
|
|
| C21 判断队列是否为空 | 武杭凯 | 冯国平 | 李聪颖 |
|
|
|
|
|
| C22 打印队列 | 李聪颖 | 武杭凯 | 冯国平 |
|
|
|
|
|
| C23 遍历队列Q | 费良荣 | 费良荣 | 李聪颖 |
|
|
|
|
|
|
|
|
|
|
工作量占比:
|
|
|
|
|
|
|
|
|
|
| 费良荣 | 冯国平 | 李聪颖 | 武杭凯 |
|
|
|
|
|
| ---- | ---- | ---- | ---- |
|
|
|
|
|
| 25 | 25 | 25 | 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 1. 系统分析
|
|
|
|
|
|
|
|
|
|
## 1.1 问题描述
|
|
|
|
|
|
|
|
|
|
设计一个银行业务模拟系统,模拟银行的业务运行并计算一天中客户在银行逗留的平均时间。银行有 $N$ 个窗口对外接待客户,从早晨银行开门起不断有客户进入银行。由于每个窗口在某个时刻只能接待一个客户,因此在客户人数多时需分别在各个窗口前排队,对于刚进入银行的客户,如果某个窗口的业务员正在空闲,则可上前办理业务;反之,若 $N$ 个窗口均有客户正在办理业务,新来的客户便会排在人数最少的队伍后面。
|
|
|
|
|
|
|
|
|
|
(1)通过人机交互的方式设定程序所需的参数:银行的开门时间和关门时间,营业窗口数目。
|
|
|
|
|
|
|
|
|
|
(2)客户的到达时间可通过人机交互、文件导入或随机生成的方式输入。
|
|
|
|
|
|
|
|
|
|
(3)保存银行营业的工作记录,存储客户的到达时间、离开时间。
|
|
|
|
|
|
|
|
|
|
(4)显示出在某一天整个银行系统中客户在银行逗留的平均时间。
|
|
|
|
|
|
|
|
|
|
要求系统运行正常、功能完整;数据结构使用得当,算法有较高的效率;代码规范、可读性高,结构清晰;具备一定的健壮性、可靠性和可维护性。
|
|
|
|
|
|
|
|
|
|
## 1.2 可行性分析
|
|
|
|
|
|
|
|
|
|
解决此问题的核心数据结构是有序链表和队列
|
|
|
|
|
|
|
|
|
|
客户排队的过程是一个按照到达时间先到先接受服务的过程,这一过程可以通过队列实现。$N$ 个不同的窗口对应 $N$ 个队列,队列中每一个元素对应一个客户。
|
|
|
|
|
|
|
|
|
|
算法中处理的事件有两类:一类是客户到来事件,另一类是客户离开事件。客户到来事件发生的时刻随客户到来自然形成,客户到来后,使用排序算法找到最短的等待队列,将客户插入队列;客户离开事件发生的时刻由银行窗口为其办理业务的完成时间决定,业务办理完成后,客户从等待队列中出队,窗口为下一个客户(如果存在的话)服务。由于程序驱动是按事件发生时刻的先后顺序进行,则事件表应是有序表,其主要操作是插入和删除事件。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 1.3 需求分析
|
|
|
|
|
|
|
|
|
|
### (1)输入和输出
|
|
|
|
|
|
|
|
|
|
主要输入:客户到达时间、客户离开时间(主要作用:确定某个客户的逗留时间)
|
|
|
|
|
某天客户的数量
|
|
|
|
|
银行的开门时间、银行的关门时间、营业窗口数目
|
|
|
|
|
主要输出:某一天整个银行系统中客户在银行逗留的平均时间
|
|
|
|
|
|
|
|
|
|
系统每次随机生成的是
|
|
|
|
|
|
|
|
|
|
1)当前顾客顾客的柜台被服务时间durtime
|
|
|
|
|
|
|
|
|
|
2)当前顾客和下一个顾客到达的间隔时间intertime
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### (2)数据字典
|
|
|
|
|
|
|
|
|
|
某个客户逗留时间=客户离开时间-客户到达时间
|
|
|
|
|
|
|
|
|
|
假设当前事件发生的时刻为occurtime,则下一个客户到达事件发生的时刻为occurtime+intertime
|
|
|
|
|
|
|
|
|
|
注:每个顾客在银行的等待时间取决于队列里前一个节点的离开时间,而不是自己的到达时间+服务时间。
|
|
|
|
|
|
|
|
|
|
### (3)数据文件
|
|
|
|
|
|
|
|
|
|
1)通过人机交互的方式设定程序所需的参数:银行的开门时间和关门时间,营业窗口数目。
|
|
|
|
|
2)系统每次随机生成的是
|
|
|
|
|
当前顾客顾客的柜台被服务时间durtime
|
|
|
|
|
当前顾客和下一个顾客到达的间隔时间intertime
|
|
|
|
|
3)客户的到达时间可通过人机交互、文件导入或随机生成的方式输入。
|
|
|
|
|
|
|
|
|
|
### (4)参数设定
|
|
|
|
|
|
|
|
|
|
通过人机交互的方式设定程序所需的参数:银行的开门时间和关门时间,营业窗口数目。
|
|
|
|
|
客户的到达时间可通过人机交互、文件导入或随机生成的方式输入。
|
|
|
|
|
|
|
|
|
|
### (5)管理客户、队列的功能
|
|
|
|
|
|
|
|
|
|
管理客户、队列的功能
|
|
|
|
|
|
|
|
|
|
主要作用:类似银行进门处的接待员,引导客户在哪里排队
|
|
|
|
|
|
|
|
|
|
# 2. 系统设计
|
|
|
|
|
|
|
|
|
|
## 2.1 概要设计
|
|
|
|
|
|
|
|
|
|
大体分为三大模块:
|
|
|
|
|
(1)客户
|
|
|
|
|
void Bank_Simulation(int CloseTime);//银行业务模拟,统计一天内客户在银行逗留的平均时间
|
|
|
|
|
|
|
|
|
|
int cmp(Event a, Event b);//比较事件发生先后
|
|
|
|
|
|
|
|
|
|
void OpenForDay();//银行开门
|
|
|
|
|
|
|
|
|
|
void OrderInsert(EventList L, Event en, int(*cmp)(Event a, Event b));//插入事件
|
|
|
|
|
|
|
|
|
|
void CustomerArrived();//客户进门
|
|
|
|
|
|
|
|
|
|
void CustomerDepature();//客户离开
|
|
|
|
|
|
|
|
|
|
int Minimum(LinkQueue Q[5]);//求长度最短队列
|
|
|
|
|
|
|
|
|
|
(2)链表
|
|
|
|
|
Status InitList(LinkList& L);//链表初始化
|
|
|
|
|
|
|
|
|
|
Status ListInsert_L(LinkList& L, int i, ElemType e);//在第i个位置之前插入元素e
|
|
|
|
|
|
|
|
|
|
Status ListEmpty(LinkList L);//判断链表是否为空
|
|
|
|
|
|
|
|
|
|
Status DelFirst(LinkList L, LNode*& q);//删除链表中第一个结点并以q返回
|
|
|
|
|
|
|
|
|
|
LNode* GetHead(LinkList L);//返回链表头结点
|
|
|
|
|
|
|
|
|
|
ElemType GetCurElem(LNode* p);//已知p指向线性链表中的一个结点,返回p所指结点中元素的值
|
|
|
|
|
|
|
|
|
|
void PrintEventList();//打印事件链表
|
|
|
|
|
|
|
|
|
|
Status ListTraverse(LinkList& L);//遍历链表
|
|
|
|
|
|
|
|
|
|
(3)链队列
|
|
|
|
|
Status InitQueue(LinkQueue& Q);//链队列的初始化
|
|
|
|
|
|
|
|
|
|
Status EnQueue(LinkQueue& Q, QElemType e);//入队
|
|
|
|
|
|
|
|
|
|
Status DeQueue(LinkQueue& Q, QElemType& e);//出队
|
|
|
|
|
|
|
|
|
|
int QueueLength(LinkQueue Q);//返回队列的长度
|
|
|
|
|
|
|
|
|
|
Status GetHead(LinkQueue Q, QElemType& e);//获取队头元素 注:由于参数个数不同,发生函数重载
|
|
|
|
|
|
|
|
|
|
Status QueueEmpty(LinkQueue Q);//判断队列是否为空
|
|
|
|
|
|
|
|
|
|
void PrintQueue();//打印队列
|
|
|
|
|
|
|
|
|
|
Status QueueTraverse(LinkQueue Q);//遍历队列Q
|
|
|
|
|
|
|
|
|
|
## 2.2 数据结构设计
|
|
|
|
|
|
|
|
|
|
采用了链表和链队列
|
|
|
|
|
|
|
|
|
|
### (1)链表结构
|
|
|
|
|
|
|
|
|
|
Status InitList(LinkList& L);//链表初始化
|
|
|
|
|
|
|
|
|
|
Status ListInsert_L(LinkList& L, int i, ElemType e);//在第i个位置之前插入元素e
|
|
|
|
|
|
|
|
|
|
Status ListEmpty(LinkList L);//判断链表是否为空
|
|
|
|
|
|
|
|
|
|
Status DelFirst(LinkList L, LNode*& q);//删除链表中第一个结点并以q返回
|
|
|
|
|
|
|
|
|
|
LNode* GetHead(LinkList L);//返回链表头结点
|
|
|
|
|
|
|
|
|
|
ElemType GetCurElem(LNode* p);//已知p指向线性链表中的一个结点,返回p所指结点中元素的值
|
|
|
|
|
|
|
|
|
|
void PrintEventList();//打印事件链表
|
|
|
|
|
|
|
|
|
|
Status ListTraverse(LinkList& L);//遍历链表
|
|
|
|
|
|
|
|
|
|
### (2)链队列结构
|
|
|
|
|
|
|
|
|
|
Status InitQueue(LinkQueue& Q);//链队列的初始化
|
|
|
|
|
|
|
|
|
|
Status EnQueue(LinkQueue& Q, QElemType e);//入队
|
|
|
|
|
|
|
|
|
|
Status DeQueue(LinkQueue& Q, QElemType& e);//出队
|
|
|
|
|
|
|
|
|
|
int QueueLength(LinkQueue Q);//返回队列的长度
|
|
|
|
|
|
|
|
|
|
Status GetHead(LinkQueue Q, QElemType& e);//获取队头元素 注:由于参数个数不同,发生函数重载
|
|
|
|
|
|
|
|
|
|
Status QueueEmpty(LinkQueue Q);//判断队列是否为空
|
|
|
|
|
|
|
|
|
|
void PrintQueue();//打印队列
|
|
|
|
|
|
|
|
|
|
Status QueueTraverse(LinkQueue Q);//遍历队列Q
|
|
|
|
|
|
|
|
|
|
###
|
|
|
|
|
|
|
|
|
|
## 2.3 算法设计
|
|
|
|
|
|
|
|
|
|
### (1)链表初始化算法
|
|
|
|
|
|
|
|
|
|
链表初始化
|
|
|
|
|
{
|
|
|
|
|
L = (LinkList)malloc(sizeof(LNode));
|
|
|
|
|
if (!L){
|
|
|
|
|
exit(OVERFLOW);}
|
|
|
|
|
L->next = NULL;
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
### (2)链表判空算法
|
|
|
|
|
|
|
|
|
|
判断链表是否为空
|
|
|
|
|
{
|
|
|
|
|
if (L->next)
|
|
|
|
|
{return FALSE;}
|
|
|
|
|
else{
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
### (3) 打印事件链表算法
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
printf("Current Eventlist is:\n");
|
|
|
|
|
ListTraverse(ev);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
### (4) 遍历链表算法
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
LNode* p = L->next;
|
|
|
|
|
if (!p) {
|
|
|
|
|
printf("List is empty.\n");
|
|
|
|
|
return ERROR;
|
|
|
|
|
}
|
|
|
|
|
while (p != NULL) {
|
|
|
|
|
printf("OccurTime:%d,Event Type:%d\n", p->data.OccurTime, p->data.NType);
|
|
|
|
|
p = p->next;
|
|
|
|
|
}
|
|
|
|
|
printf("\n");
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
### (5) 链队列初始化算法
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));
|
|
|
|
|
if (!Q.front)
|
|
|
|
|
{exit(OVERFLOW);}
|
|
|
|
|
Q.front->next = NULL;
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
### (6) 队列判空算法
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
if (Q.front == Q.rear)
|
|
|
|
|
{
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
### (7) 打印队列算法
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 1; i <= 4; i++) {
|
|
|
|
|
printf("窗口 %d 有 %d 个客户:", i, QueueLength(q[i]));
|
|
|
|
|
QueueTraverse(q[i]);
|
|
|
|
|
}
|
|
|
|
|
printf("\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
### (8) 遍历队列算法
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
QNode* p = Q.front->next;
|
|
|
|
|
if (!p) {
|
|
|
|
|
printf("--Is empty.\n");
|
|
|
|
|
return ERROR;
|
|
|
|
|
}
|
|
|
|
|
while (p) {
|
|
|
|
|
printf("(到达时刻 %d min 办理业务需要花费 %d min) ", p->data.ArrivalTime, p->data.Duration);
|
|
|
|
|
p = p->next;
|
|
|
|
|
}
|
|
|
|
|
printf("\n");
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 3. 系统实现
|
|
|
|
|
|
|
|
|
|
项目采用 C 语言编程实现,在 VS Code 集成开发环境(IDE)中用 GCC 进行编译。系统采用模块化设计,程序结构清晰
|
|
|
|
|
|
|
|
|
|
void Bank_Simulation(int CloseTime);//银行业务模拟,统计一天内客户在银行逗留的平均时间
|
|
|
|
|
int cmp(Event a, Event b);//比较事件发生先后
|
|
|
|
|
void OpenForDay();//银行开门
|
|
|
|
|
void OrderInsert(EventList L, Event en, int(*cmp)(Event a, Event b));//插入事件
|
|
|
|
|
void CustomerArrived();//客户进门
|
|
|
|
|
void CustomerDepature();//客户离开
|
|
|
|
|
int Minimum(LinkQueue Q[5]);//求长度最短队列
|
|
|
|
|
Status InitList(LinkList& L);//链表初始化
|
|
|
|
|
Status ListInsert_L(LinkList& L, int i, ElemType e);//在第i个位置之前插入元素e
|
|
|
|
|
Status ListEmpty(LinkList L);//判断链表是否为空
|
|
|
|
|
Status DelFirst(LinkList L, LNode*& q);//删除链表中第一个结点并以q返回
|
|
|
|
|
LNode* GetHead(LinkList L);//返回链表头结点
|
|
|
|
|
ElemType GetCurElem(LNode* p);//已知p指向线性链表中的一个结点,返回p所指结点中元素的值
|
|
|
|
|
void PrintEventList();//打印事件链表
|
|
|
|
|
Status ListTraverse(LinkList& L);//遍历链表
|
|
|
|
|
Status InitQueue(LinkQueue& Q);//链队列的初始化
|
|
|
|
|
Status EnQueue(LinkQueue& Q, QElemType e);//入队
|
|
|
|
|
Status DeQueue(LinkQueue& Q, QElemType& e);//出队
|
|
|
|
|
int QueueLength(LinkQueue Q);//返回队列的长度
|
|
|
|
|
Status GetHead(LinkQueue Q, QElemType& e);//获取队头元素 注:由于参数个数不同,发生函数重载
|
|
|
|
|
Status QueueEmpty(LinkQueue Q);//判断队列是否为空
|
|
|
|
|
void PrintQueue();//打印队列
|
|
|
|
|
Status QueueTraverse(LinkQueue Q);//遍历队列Q
|
|
|
|
|
|
|
|
|
|
## 3.1 核心数据结构的实现
|
|
|
|
|
|
|
|
|
|
主要为有序链表和队列两种数据类型
|
|
|
|
|
|
|
|
|
|
配合程序代码加以说明。如下:
|
|
|
|
|
```cpp
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
int OccurTime;//事件发生时刻
|
|
|
|
|
int NType;//事件类型,0表示到达事件,1-4表示四个窗口的离开事件
|
|
|
|
|
}Event, ElemType;
|
|
|
|
|
|
|
|
|
|
typedef struct LNode
|
|
|
|
|
{
|
|
|
|
|
ElemType data;
|
|
|
|
|
struct LNode* next;
|
|
|
|
|
}LNode, * LinkList;
|
|
|
|
|
|
|
|
|
|
typedef LinkList EventList;
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
int ArrivalTime;//到达时刻
|
|
|
|
|
int Duration;//办理事务所需事件
|
|
|
|
|
}QElemType;
|
|
|
|
|
|
|
|
|
|
typedef struct QNode
|
|
|
|
|
{
|
|
|
|
|
QElemType data;
|
|
|
|
|
struct QNode* next;
|
|
|
|
|
}QNode, * QueuePtr;
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
QueuePtr front;//队头指针
|
|
|
|
|
QueuePtr rear;//队尾指针
|
|
|
|
|
}LinkQueue;
|
|
|
|
|
|
|
|
|
|
EventList ev;//事件表
|
|
|
|
|
Event en;//事件
|
|
|
|
|
LinkQueue q[5];//四个客户队列
|
|
|
|
|
QElemType customer;//客户记录
|
|
|
|
|
int TotalTime, CustomerNum, CloseTime;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 3.2 核心算法的实现
|
|
|
|
|
|
|
|
|
|
程序代码加以说明。如下:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
void Bank_Simulation(int CloseTime)//银行业务模拟,统计一天内客户在银行逗留的平均时间
|
|
|
|
|
{
|
|
|
|
|
OpenForDay();//开始营业
|
|
|
|
|
LNode* p;
|
|
|
|
|
while (!ListEmpty(ev))
|
|
|
|
|
{
|
|
|
|
|
DelFirst(GetHead(ev), p);
|
|
|
|
|
printf("********action********\n");
|
|
|
|
|
en = GetCurElem(p);
|
|
|
|
|
if (en.NType == 0)
|
|
|
|
|
{
|
|
|
|
|
CustomerArrived();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
CustomerDepature();
|
|
|
|
|
}
|
|
|
|
|
PrintQueue();
|
|
|
|
|
PrintEventList();
|
|
|
|
|
}
|
|
|
|
|
printf("The Average Time is %f\n", (float)TotalTime / CustomerNum);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int cmp(Event a, Event b)//比较事件发生先后
|
|
|
|
|
{
|
|
|
|
|
if (a.OccurTime > b.OccurTime) return 1;
|
|
|
|
|
if (a.OccurTime = b.OccurTime) return 0;
|
|
|
|
|
if (a.OccurTime < b.OccurTime) return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void OpenForDay()//银行开门
|
|
|
|
|
//初始化操作
|
|
|
|
|
{
|
|
|
|
|
TotalTime = 0;//初始化累计时间为0
|
|
|
|
|
CustomerNum = 0;//初始化客户数为0
|
|
|
|
|
InitList(ev);//初始化事件链表为空表
|
|
|
|
|
en.OccurTime = 0;
|
|
|
|
|
en.NType = 0;//设定第一个客户到达事件
|
|
|
|
|
OrderInsert(ev, en, cmp);
|
|
|
|
|
for (int i = 1; i <= 4; i++)
|
|
|
|
|
{
|
|
|
|
|
InitQueue(q[i]);//将四个银行窗口队列初始化
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void OrderInsert(EventList L, Event en, int(*cmp)(Event a, Event b))//插入事件
|
|
|
|
|
//事件插入函数,将不同事件按发生时间递增排序
|
|
|
|
|
{
|
|
|
|
|
LNode* p = L;
|
|
|
|
|
int i = 1;
|
|
|
|
|
while (p->next && cmp(en, p->next->data) > 0)//找到事件发生时间所在事件链表中的位置
|
|
|
|
|
{
|
|
|
|
|
p = p->next;
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
ListInsert_L(ev, i, en);//插入该事件
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void CustomerArrived()//客户进门
|
|
|
|
|
//处理客户到达事件,en.NType=0
|
|
|
|
|
{
|
|
|
|
|
CustomerNum++;
|
|
|
|
|
int durtime = rand() % 30 + 1;//客户处理事务时间
|
|
|
|
|
int intertime = rand() % 8;//下一个客户到达的时间间隔
|
|
|
|
|
int t = en.OccurTime + intertime;//下一个客户到达的时刻
|
|
|
|
|
if (t < CloseTime)//如果他在营业时间内进来
|
|
|
|
|
{
|
|
|
|
|
printf("一个新客户在银行营业%2dmin后进来,办理业务花费了%2dmin,下一个客户过了%2dmin后进来\n", en.OccurTime, durtime, intertime);
|
|
|
|
|
OrderInsert(ev, { t, 0 }, cmp);//插入客户进门事件,NType=0为到达事件
|
|
|
|
|
}
|
|
|
|
|
int i = Minimum(q);//客户找最短队开始排队
|
|
|
|
|
EnQueue(q[i], { en.OccurTime, durtime });
|
|
|
|
|
if (QueueLength(q[i]) == 1)
|
|
|
|
|
{
|
|
|
|
|
OrderInsert(ev, { en.OccurTime + durtime,i }, cmp);//队列长度为1时,设定一个离开事件
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void CustomerDepature()//客户离开
|
|
|
|
|
{
|
|
|
|
|
int i = en.NType;
|
|
|
|
|
DeQueue(q[i], customer);//删除第i队列的排头客户
|
|
|
|
|
TotalTime += en.OccurTime - customer.ArrivalTime;//累计客户逗留时间
|
|
|
|
|
if (!QueueEmpty(q[i])) {
|
|
|
|
|
GetHead(q[i], customer);
|
|
|
|
|
OrderInsert(ev, { en.OccurTime + customer.Duration, i }, cmp);//插入事件
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int Minimum(LinkQueue Q[5])//求长度最短队列
|
|
|
|
|
{
|
|
|
|
|
int minLength = QueueLength(Q[1]);
|
|
|
|
|
int i = 1;
|
|
|
|
|
for (int j = 2; j < 5; j++)
|
|
|
|
|
{
|
|
|
|
|
if (minLength > QueueLength(Q[j]))
|
|
|
|
|
{
|
|
|
|
|
minLength = QueueLength(Q[j]);
|
|
|
|
|
i = j;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status InitList(LinkList& L)//链表初始化
|
|
|
|
|
{
|
|
|
|
|
L = (LinkList)malloc(sizeof(LNode));
|
|
|
|
|
if (!L)
|
|
|
|
|
{
|
|
|
|
|
exit(OVERFLOW);
|
|
|
|
|
}
|
|
|
|
|
L->next = NULL;
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status ListInsert_L(LinkList& L, int i, ElemType e)//在第i个位置之前插入元素e
|
|
|
|
|
{
|
|
|
|
|
LinkList p = L;
|
|
|
|
|
int j = 0;
|
|
|
|
|
while (p && j < i - 1)//注意是i-1,因为要找被插入元素的前一个元素
|
|
|
|
|
{
|
|
|
|
|
p = p->next;
|
|
|
|
|
j++;
|
|
|
|
|
}
|
|
|
|
|
if (!p || j > i - 1)
|
|
|
|
|
{
|
|
|
|
|
return ERROR;
|
|
|
|
|
}
|
|
|
|
|
LinkList s = (LinkList)malloc(sizeof(LNode));
|
|
|
|
|
if (!s)
|
|
|
|
|
{
|
|
|
|
|
exit(OVERFLOW);
|
|
|
|
|
}
|
|
|
|
|
s->data = e;
|
|
|
|
|
s->next = p->next;
|
|
|
|
|
p->next = s;
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status ListEmpty(LinkList L)//判断链表是否为空
|
|
|
|
|
//空表:头指针和头结点仍然存在,但头结点指向NULL
|
|
|
|
|
{
|
|
|
|
|
if (L->next)
|
|
|
|
|
{
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status DelFirst(LinkList L, LNode*& q)//删除链表中第一个结点并以q返回
|
|
|
|
|
{
|
|
|
|
|
if (!L->next)
|
|
|
|
|
{
|
|
|
|
|
return ERROR;
|
|
|
|
|
}
|
|
|
|
|
q = L->next;
|
|
|
|
|
L->next = q->next;
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LNode* GetHead(LinkList L)//返回链表头结点
|
|
|
|
|
{
|
|
|
|
|
return L;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ElemType GetCurElem(LNode* p)//已知p指向线性链表中的一个结点,返回p所指结点中元素的值
|
|
|
|
|
{
|
|
|
|
|
return p->data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void PrintEventList()//打印事件链表
|
|
|
|
|
{
|
|
|
|
|
printf("Current Eventlist is:\n");
|
|
|
|
|
ListTraverse(ev);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status ListTraverse(LinkList& L) //遍历链表
|
|
|
|
|
{
|
|
|
|
|
LNode* p = L->next;
|
|
|
|
|
if (!p) {
|
|
|
|
|
printf("List is empty.\n");
|
|
|
|
|
return ERROR;
|
|
|
|
|
}
|
|
|
|
|
while (p != NULL) {
|
|
|
|
|
printf("OccurTime:%d,Event Type:%d\n", p->data.OccurTime, p->data.NType);
|
|
|
|
|
p = p->next;
|
|
|
|
|
}
|
|
|
|
|
printf("\n");
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status InitQueue(LinkQueue& Q)//链队列的初始化
|
|
|
|
|
{
|
|
|
|
|
Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));
|
|
|
|
|
if (!Q.front)
|
|
|
|
|
{
|
|
|
|
|
exit(OVERFLOW);
|
|
|
|
|
}
|
|
|
|
|
Q.front->next = NULL;
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status EnQueue(LinkQueue& Q, QElemType e)//入队
|
|
|
|
|
{
|
|
|
|
|
QNode* 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)//出队
|
|
|
|
|
{
|
|
|
|
|
if (Q.front == Q.rear)
|
|
|
|
|
{
|
|
|
|
|
return ERROR;
|
|
|
|
|
}
|
|
|
|
|
QNode* p = Q.front->next;
|
|
|
|
|
e = p->data;
|
|
|
|
|
Q.front->next = p->next;
|
|
|
|
|
if (Q.rear == p)//注意这里要考虑到,当队列中最后一个元素被删后,队列尾指针也丢失了,因此需对队尾指针重新复制(指向头结点)
|
|
|
|
|
{
|
|
|
|
|
Q.rear = Q.front;
|
|
|
|
|
}
|
|
|
|
|
free(p);
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int QueueLength(LinkQueue Q)//返回队列的长度
|
|
|
|
|
{
|
|
|
|
|
int count = 0;
|
|
|
|
|
QNode* p = Q.front->next;
|
|
|
|
|
while (p) {
|
|
|
|
|
p = p->next;
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status GetHead(LinkQueue Q, QElemType& e)//获取队头元素
|
|
|
|
|
{
|
|
|
|
|
if (Q.front == Q.rear)
|
|
|
|
|
{
|
|
|
|
|
return ERROR;
|
|
|
|
|
}
|
|
|
|
|
e = Q.front->next->data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status QueueEmpty(LinkQueue Q)//判断队列是否为空
|
|
|
|
|
{
|
|
|
|
|
if (Q.front == Q.rear)
|
|
|
|
|
{
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void PrintQueue()//打印队列
|
|
|
|
|
{
|
|
|
|
|
//打印当前队列
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 1; i <= 4; i++) {
|
|
|
|
|
printf("窗口 %d 有 %d 个客户:", i, QueueLength(q[i]));
|
|
|
|
|
QueueTraverse(q[i]);
|
|
|
|
|
}
|
|
|
|
|
printf("\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status QueueTraverse(LinkQueue Q)//遍历队列Q
|
|
|
|
|
{
|
|
|
|
|
QNode* p = Q.front->next;
|
|
|
|
|
if (!p) {
|
|
|
|
|
printf("--Is empty.\n");
|
|
|
|
|
return ERROR;
|
|
|
|
|
}
|
|
|
|
|
while (p) {
|
|
|
|
|
printf("(到达时刻 %d min 办理业务需要花费 %d min) ", p->data.ArrivalTime, p->data.Duration);
|
|
|
|
|
p = p->next;
|
|
|
|
|
}
|
|
|
|
|
printf("\n");
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 4. 系统测试
|
|
|
|
|
|
|
|
|
|
![测试图](cheshi.svg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 5. 总结
|
|
|
|
|
|
|
|
|
|
本次项目综合运用线性表、队列、排序、随机数等数据结构知识,模拟银行业务系统的运行情况,掌握和提高分析、设计、实现及测试程序的综合能力。
|
|
|
|
|
|
|
|
|
|
个人小结:
|
|
|
|
|
|
|
|
|
|
成员1(费良荣):
|
|
|
|
|
|
|
|
|
|
成员2(冯国平):
|
|
|
|
|
|
|
|
|
|
成员3(李聪颖):
|
|
|
|
|
|
|
|
|
|
成员4(武杭凯):
|
|
|
|
|
|
|
|
|
|
# 参考文献
|
|
|
|
|
|
|
|
|
|
[1] 严蔚敏, 吴伟民. 数据结构(C语言版). 北京: 清华大学出版社, 2007.
|
|
|
|
|
[2] 作者:回到唐朝当少爷 https://www.bilibili.com/read/cv15955830 出处:bilibili
|
|
|
|
|
|
|
|
|
|
|