You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
text2/README.md

596 lines
22 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 银行业务模拟系统
2023.6.18
刘彩月,刘梦琦,金雨佳,赵亚璇
摘要:针对银行营业的工作记录和计算一天中客户在银行的逗留时间的问题。实现了模拟客户排队,窗口,管理客户,队列的功能。为了有效的存储和处理客户(客户到达,客户离开)采用有序表,其主要操作是插入和删除事件,用一个单链表表示。为了有效存储和处理窗口( 客户到达时间,离开时间)采用了队列。为了解决客户找到最短等待队列使用了排序和插入算法为了解决客户离开事件使用了删除算法。删除和插入的算法效率为On。针对银行开门关门营业窗口数目客户到达时间采用人机交互。完成了保存银行营业的工作记录储存客户的到达时间和离开时间显示出在某一天整个银行系统中客户在银行逗留的平均时间。
项目开发过程中采用Kanban (看板)进行任务管理和分工协作并使用Git对程序代码和文档进行版本管理。
任务分工及完成情况:
|任务|设计|开发|测试|文档|
|-----|----|----|----|----|
| 1.系统分析和4.系统测试| 刘彩月 | 刘彩月 | 赵亚璇 | 刘彩月 |
| 2.1概要设计 | 赵亚璇 | 赵亚璇 | 金雨佳 | 赵亚璇 |
| 2.2数据结构设计和2.3 算法设计| 金雨佳 | 金雨佳 | 刘梦琦 | 金雨佳 |
| 3.系统实现 | 刘梦琦 | 刘梦琦 | 刘彩月| 刘梦琦 |
|代码|金雨佳|刘梦琦|赵亚璇|刘彩月|
每个成员的工作量(百分比):
|刘彩月|赵亚璇|金雨佳|刘梦琦|
|------|-----|-----|-----|
| 25 | 25 | 25 | 25 |
# 1.系统分析
## 1.1问题描述
银行有n个窗口对外接待客户从早晨银行开门起不断有客户进入银行。由于每个窗口在某时刻只能接待一个客户因此在客户人多时需分别在各个窗口前排队。对于刚进入银行的客户如果某个窗口的营业员正在空闲则可上前办理业务反之若n个窗口均有客户正在办理业务新来的客户便会排在人数最少的队伍后面。
设计一个银行业务模拟系统,模拟银行的业务运行并计算一天中客户在银行逗留的平均时间。
1通过人机互交的方式设定程序所需要的参数银行的开门时间和关门时间营业窗口数目。
2客户的到达时间可通过人机互交文件导入或随机产生的方式输入。
3保存银行营业的工作记录储存客户的到达时间和离开时间。
4显示出在某一天整个银行系统中客户在银行逗留的平均时间。
要求系统运行正常,功能完整;数据结构使用得当,算法有较高的效率;代码规范,可读性高,结构清晰,具有一定的健壮性,可靠性和可维护性。
## 1.2可行性分析
运用了单链表和链队列,运用了排序,插入和删除的算法。
客户排队的过程是一个按照到达时间先到先接受服务的过程这一过程可以通过队列实现。n个不同的窗口对应n个队列队列中每一个元素对应一个客户。
算法中处理的事件有两类:一类是客户到来事件,另一类是客户离开事件。客户到来事件发生的时刻随客户到来自然形成,客户到来后,使用排序算法找到最短的等待队列,将客户插入队列;客户离开事件发生的时刻由银行窗口为其办理业务的完成时间决定,业务办理完成后,客户从等待队列中出队,窗口为下一个客户(如果存在的话)服务。
本项目涉及的对象主要包括客户、银行窗口,可以设计两个数据结构实现客户和窗口的功能。此外,管理客户、队列的功能可以单独实现(类似银行进门处的接待员,引导客户在哪里排队)。
## 1.3 需求分析
### 1输入和输出
主要输入:银行的营业时间
主要输出:根据随机数输出实时银行运行状态;输出在银行营业时间内客户在银行逗留的平均时间。
随机数:
1客户办理业务的时间当前客户
2当前客户和下一个客户到达的间隔时间
### 2数据字典
描述系统中需要处理的所有数据包含的具体信息。例如:
OccurTime表示事件发生的时间
NType表示客户的状态办理业务中或离开
ArrivalTime表示客户到达时间
CustomerNum记录客户的数目
CloseTime银行关门时间
durtime客户办理业务的时间
每个客户在银行的等待时间取决于队列里前一个节点的离开时间,而不是自己的到达时间+服务时间即en.OccurTime + intertime;
在银行营业时间内总的办理业务的时间TotalTime += en.OccurTime - customer.ArrivalTime;
### 3数据文件
1当前客户的柜台被服务时间当前客户和下一个客户到达的间隔时间用随机产生的方式输入。
2保存银行营业的工作记录储存客户的到达时间和离开时间。
3显示出在某一天整个银行系统中客户在银行逗留的平均时间。
### 4参数设定
提示信息:请输入银行的营业时间(min)
用户输入:银行的营业时间(min)
### 5链表功能
该功能的主要作用是:给客户分配银行窗口
### 6链队列功能
该功能的主要作用是是:让客户有序高效办理业务
# 2. 系统设计
## 2.1 概要设计
#### 主程序的流程
主程序先是让外部进行测试数据输入,待测试数据输入完后,执行银行业务模拟系统,产生客户逗留平均时间。
###### 其具体操作如下:
1.以单链表表示模拟客户排队,窗口,管理客户,队列的功能。
2.以有序表的插入和删除操作来存储和处理客户到达和客户离开两种情况(客户)。
3.用队列来有效存储和处理客户到达时间和离开时间(窗口)。
4.以队列的排序和插入算法解决客户找到最短等待队列。
5.使用了删除算法来解决客户的离开事件删除和插入的算法效率为On
6.采用人机交互来针对银行开门,关门,营业窗口数目,客户到达时间等问题。
7.保存银行营业的工作记录,储存客户的到达时间和离开时间。
8.显示出在某一天整个银行系统中客户在银行逗留的平均时间。
###### 以上项目步骤可用代码大体分为以下三个模块:
```
(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(LinkList& L,int i,ElemType e);//在第i个位置前即i-1的位置上插入元素e
Status ListEmpty(LinkList L);//判断链表是否为空
Status DelFirst(LinkList L,LNode*& q);//删除链表中第一个结点并以q返回
LNode* GetHead(LinkList L);//返回链表头结点
ElemType GetCurElem(LNode* 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);//遍历队列
## 2.3 算法设计
主要运用排序、链表、队列、判断最短队列、比较事件先后等算法完成银行业务的模拟,体现了整个业务的可行性、完整性、简洁性、高效性。
### 1链表判空算法
````
判断链表是否为空
If链表为空
返回FALSE
ELSE
返回TRUE
````
### 2银行业务模拟算法
````
引入OpenForDay函数
WHILE链表非空
引入DelFrist函数、GetCurElem函数、GetHead函数、CustomerArrived函数、CustomerDepature函数等计算客户平均逗留时间
````
### 3比较事件先后算法
````
IF a事件时间>b事件时间
返回 1
IF a事件时间=b事件时间
返回 0
IF a事件时间< b事件时间
返回-1
````
### 4求最短队列算法
````
初始化最短队列为Q[1]
FOR 从Q[2]开始循环
IF 最短长度>Q[j]
最短长度=Q[j]
返回队列j
````
# 3. 系统实现
语言:C语言
开发工具:VS Code
采用模块化设计,通过大量的函数实现,银行开门、客户进门、客户离开等功能进而统计一天内客户在银行逗留的平均时间
## 3.1 核心数据结构的实现
首先通过有序链表,实现一部分功能如:先初始化有序表在第i个位置之前插入元素e删除链表中第一个结点并以q返回已知p指向线性链表中的一个结点返回p所指结点中元素的值等等。其次通过队列实现入队和出队。
```
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;
```
链表
【1】通过一个个指针将节点串起来
【2】对于元素的随机访问需要使用计数器来访问指定的元素并且只能从头节点开始访问每访问一个节点计数器加1直到给定的“下标”
【3】增加元素和删除元素的效率很高
队列
【1】先进先出
【2】线程池中的线程就是从任务队列中取出任务
## 3.2 核心算法的实现
```
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.系统测试
```
请输入银行的营业时间(min)30
********action********
一个新客户在银行营业 0min后进来办理业务花费了25min下一个客户过了 1min后进来
窗口 1 有 1 个客户:(到达时刻 0 min 办理业务需要花费 25 min)
窗口 2 有 0 个客户:--Is empty.
窗口 3 有 0 个客户:--Is empty.
窗口 4 有 0 个客户:--Is empty.
Current Eventlist is:
OccurTime:1,Event Type:0
OccurTime:25,Event Type:1
********action********
一个新客户在银行营业 1min后进来办理业务花费了 6min下一个客户过了 7min后进来
窗口 1 有 1 个客户:(到达时刻 0 min 办理业务需要花费 25 min)
窗口 2 有 1 个客户:(到达时刻 1 min 办理业务需要花费 6 min)
窗口 3 有 0 个客户:--Is empty.
窗口 4 有 0 个客户:--Is empty.
Current Eventlist is:
OccurTime:7,Event Type:2
OccurTime:8,Event Type:0
OccurTime:25,Event Type:1
********action********
窗口 1 有 1 个客户:(到达时刻 0 min 办理业务需要花费 25 min)
窗口 2 有 0 个客户:--Is empty.
窗口 3 有 0 个客户:--Is empty.
窗口 4 有 0 个客户:--Is empty.
Current Eventlist is:
OccurTime:8,Event Type:0
OccurTime:25,Event Type:1
********action********
一个新客户在银行营业 8min后进来办理业务花费了 6min下一个客户过了 7min后进来
窗口 1 有 1 个客户:(到达时刻 0 min 办理业务需要花费 25 min)
窗口 2 有 1 个客户:(到达时刻 8 min 办理业务需要花费 6 min)
窗口 3 有 0 个客户:--Is empty.
窗口 4 有 0 个客户:--Is empty.
Current Eventlist is:
OccurTime:14,Event Type:2
OccurTime:15,Event Type:0
OccurTime:25,Event Type:1
********action********
窗口 1 有 1 个客户:(到达时刻 0 min 办理业务需要花费 25 min)
窗口 2 有 0 个客户:--Is empty.
窗口 3 有 0 个客户:--Is empty.
窗口 4 有 0 个客户:--Is empty.
Current Eventlist is:
OccurTime:15,Event Type:0
OccurTime:25,Event Type:1
********action********
一个新客户在银行营业15min后进来办理业务花费了25min下一个客户过了 0min后进来
窗口 1 有 1 个客户:(到达时刻 0 min 办理业务需要花费 25 min)
窗口 2 有 1 个客户:(到达时刻 15 min 办理业务需要花费 25 min)
窗口 3 有 0 个客户:--Is empty.
窗口 4 有 0 个客户:--Is empty.
Current Eventlist is:
OccurTime:15,Event Type:0
OccurTime:25,Event Type:1
OccurTime:40,Event Type:2
********action********
一个新客户在银行营业15min后进来办理业务花费了 5min下一个客户过了 7min后进来
窗口 1 有 1 个客户:(到达时刻 0 min 办理业务需要花费 25 min)
窗口 2 有 1 个客户:(到达时刻 15 min 办理业务需要花费 25 min)
窗口 3 有 1 个客户:(到达时刻 15 min 办理业务需要花费 5 min)
窗口 4 有 0 个客户:--Is empty.
Current Eventlist is:
OccurTime:20,Event Type:3
OccurTime:22,Event Type:0
OccurTime:25,Event Type:1
OccurTime:40,Event Type:2
********action********
窗口 1 有 1 个客户:(到达时刻 0 min 办理业务需要花费 25 min)
窗口 2 有 1 个客户:(到达时刻 15 min 办理业务需要花费 25 min)
窗口 3 有 0 个客户:--Is empty.
窗口 4 有 0 个客户:--Is empty.
Current Eventlist is:
OccurTime:22,Event Type:0
OccurTime:25,Event Type:1
OccurTime:40,Event Type:2
********action********
一个新客户在银行营业22min后进来办理业务花费了13min下一个客户过了 3min后进来
窗口 1 有 1 个客户:(到达时刻 0 min 办理业务需要花费 25 min)
窗口 2 有 1 个客户:(到达时刻 15 min 办理业务需要花费 25 min)
窗口 3 有 1 个客户:(到达时刻 22 min 办理业务需要花费 13 min)
窗口 4 有 0 个客户:--Is empty.
Current Eventlist is:
OccurTime:25,Event Type:0
OccurTime:25,Event Type:1
OccurTime:35,Event Type:3
OccurTime:40,Event Type:2
********action********
窗口 1 有 1 个客户:(到达时刻 0 min 办理业务需要花费 25 min)
窗口 2 有 1 个客户:(到达时刻 15 min 办理业务需要花费 25 min)
窗口 3 有 1 个客户:(到达时刻 22 min 办理业务需要花费 13 min)
窗口 4 有 1 个客户:(到达时刻 25 min 办理业务需要花费 27 min)
Current Eventlist is:
OccurTime:25,Event Type:1
OccurTime:35,Event Type:3
OccurTime:40,Event Type:2
OccurTime:52,Event Type:4
********action********
窗口 1 有 0 个客户:--Is empty.
窗口 2 有 1 个客户:(到达时刻 15 min 办理业务需要花费 25 min)
窗口 3 有 1 个客户:(到达时刻 22 min 办理业务需要花费 13 min)
窗口 4 有 1 个客户:(到达时刻 25 min 办理业务需要花费 27 min)
Current Eventlist is:
OccurTime:35,Event Type:3
OccurTime:40,Event Type:2
OccurTime:52,Event Type:4
********action********
窗口 1 有 0 个客户:--Is empty.
窗口 2 有 1 个客户:(到达时刻 15 min 办理业务需要花费 25 min)
Current Eventlist is:
List is empty.
The Average Time is 15.285714
```
# 5. 总结
本项目是模拟银行业务的相关问题,通过本次项目我们用单链表和链队列的相关知识完成了银行客户的排队问题。提高了我们的团队协作能力和问题分析能力。
遇到的问题:代码的实现问题
解决方法:通过查阅严蔚敏, 吴伟民的数据结构和B站的视频和代码来理解项目的过程。
个人小结:
成员1刘彩月:
经过这次数据结构的实训,我认识到了数据结构和生活应用的联系。用数据结构中的队列和单链表的知识,可以模拟解决银行的排队问题。同时我也感受到了个人技术和团队合作的重要性,在学习数据结构的同时,我应该联合生活中的一些具体的问题,联系具体问题来巩固数据结构的相关知识;通过数据结构解决生活中的相关问题。努力提高自己的个人技术,提高自己的编程能力。良好的团队合作往往可以达到事半功倍的效果,我还应该注重团队合作的重要性。和队员之间相互合作,相互信任等都是团队合作中最基础的。
成员2金雨佳
数据结构是本专业一门重要的专业基础课,是一门关键性核心课程。通过本次的项目实训,对基础知识起到了复习巩固的作用,尤其是对于队列和链表的相关知识。我们队由一点一点零散的知识搭建成一个完整的银行模拟系统,实现了由书面知识转变成解决现实问题。这个过程中在代码的实现、各种小工具的使用中等遇到了不少问题,但在解决问题中学到了很多实践性的知识。同时使我更加深刻的认识到了知识的实用性和团队合作的重要性。
成员3赵亚璇
在项目的前期准备阶段,大家一起讨论,跟随课程的推进,使用课程中学到的各种知识,将整个项目的架构逐渐搭建起来,层次和逻辑也越来越清晰。在这一阶段时间,学习的过程虽然不容易,但确实学到了不少实用的知识。在运用相关框架进行实践开发的过程中,也遇到了各种各样的问题,通过查找资料和不断的尝试也都逐一解决了,这一过程自己的能力也得到了锻炼,有不错的成就感。
成员4刘梦琦
通过这次数据结构实训,让我意识到了团队协作的重要性,运用数据结构的知识模拟银行系统,更加熟练地掌握单链表和队列的应用,将所学的知识用在实践项目上时我此次实训的最大收获,在未来的生活中,我要努力学习技术,将知识联系到实际生活当中,把所学用到所用。
# 参考文献
[1] 严蔚敏, 吴伟民. 数据结构C语言版. 北京: 清华大学出版社, 2007.
[2]回到唐朝当少爷 https://www.bilibili.com/read/cv15955830?from=search&spm_id_from=333.337.0.0