# 银行业务模拟系统 2023.6.18 刘彩月,刘梦琦,金雨佳,赵亚璇 摘要:针对银行营业的工作记录和计算一天中客户在银行的逗留时间的问题。实现了模拟客户排队,窗口,管理客户,队列的功能。为了有效的存储和处理客户(客户到达,客户离开)采用有序表,其主要操作是插入和删除事件,用一个单链表表示。为了有效存储和处理窗口( 客户到达时间,离开时间)采用了队列。为了解决客户找到最短等待队列,使用了排序和插入算法,为了解决客户离开事件,使用了删除算法。删除和插入的算法效率为O(n)。针对银行开门,关门,营业窗口数目,客户到达时间,采用人机交互。完成了保存银行营业的工作记录,储存客户的到达时间和离开时间;显示出在某一天整个银行系统中客户在银行逗留的平均时间。 项目开发过程中采用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.使用了删除算法来解决客户的离开事件,删除和插入的算法效率为O(n)。 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)xxx结构 给出核心数据结构的设计,包括文字描述和示意图。讲清楚数据是如何组织的。多个数据结构,逐一列出。 ### (2)xxx结构 给出核心数据结构的设计,包括文字描述和示意图。讲清楚数据是如何组织的。 ## 2.3 算法设计 首先,分析对比几种可选的算法设计方案。如是否排序,广度优先或深度优先搜索等。给出每一种设计方案的特点(优势、不足)。然后,综合考虑各种因素(空间、时间、乃至团队成员的水平等),给出你的选择。 ### (1)XXX算法 给出核心算法的设计,包括伪代码或流程图。多个核心算法,逐一列出。只列举解决问题的核心算法,重点讲清楚是如何解决问题的。 ### (2)XXX算法 给出核心算法的设计,包括伪代码或流程图。 # 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