diff --git a/README.md b/README.md index cf41f43..bc74773 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,18 @@ 2023.6.18 刘彩月,刘梦琦,金雨佳,赵亚璇 -摘要:本项目针对模拟银行的业务运行并计算一天中客户在银行逗留的平均时间问题,实现了 功能。为了有效地存储和处理何种数据,采用了何种数据结构。为了解决什么问题,采用了什么算法,算法效率如何。针对其他特定需求做了哪些工作。项目的整体效果如何,有何亮点和创新。 - +摘要:针对银行营业的工作记录和计算一天中客户在银行的逗留时间的问题。实现了模拟客户排队,窗口,管理客户,队列的功能。为了有效的存储和处理客户(客户到达,客户离开)采用有序表,其主要操作是插入和删除事件,用一个单链表表示。为了有效存储和处理窗口( 客户到达时间,离开时间)采用了队列。为了解决客户找到最短等待队列,使用了排序和插入算法,为了解决客户离开事件,使用了删除算法。删除和插入的算法效率为O(n)。针对银行开门,关门,营业窗口数目,客户到达时间,采用人机交互。完成了保存银行营业的工作记录,储存客户的到达时间和离开时间;显示出在某一天整个银行系统中客户在银行逗留的平均时间。 任务分工及完成情况: |任务|设计|开发|测试|文档| |-----|----|----|----|----| -| | | | | | -| | | | | | -| | | | | | -| | | | | | +| 1和4| 刘彩月 | 刘彩月 | 赵亚璇 | 刘彩月 | +| 2.1 | 赵亚璇 | 赵亚璇 | 金雨佳 | 赵亚璇 | +| 2.2和2.3 | 金雨佳 | 金雨佳 | 刘梦琦 | 金雨佳 | +| 3 | 刘梦琦 | 刘梦琦 | 刘彩月| 刘梦琦 | +|代码|金雨佳|刘梦琦|赵亚璇|刘彩月| + +记得写5自己的部分(个人总结) 每个成员的工作量(百分比): @@ -25,12 +27,15 @@ ## 1.1问题描述 -问题描述和具体要求。 - +银行有n个窗口对外接待客户,从早晨银行开门起不断有客户进入银行。由于每个窗口在某时刻只能接待一个客户,因此在客户人多时需分别在各个窗口前排队。对于刚进入银行的客户,如果某个窗口的营业员正在空闲,则可上前办理业务;反之,若n个窗口均有客户正在办理业务,新来的客户便会排在人数最少的队伍后面。 +设计一个银行业务模拟系统,模拟银行的业务运行并计算一天中客户在银行逗留的平均时间。通过人机互交的方式设定程序所需要的参数:银行的开门时间和关门时间,营业窗口数目。客户的到达时间可通过人机互交,文件导入或随机产生的方式输入。保存银行营业的工作记录,储存客户的到达时间和离开时间。显示出在某一天整个银行系统中客户在银行逗留的平均时间。要求系统运行正常,功能完整;数据结构使用得当,算法有较高的效率;代码规范,可读性高,结构清晰,具有一定的健壮性,可靠性和可维护性。 ## 1.2可行性分析 明确解决问题的关键,核心数据结构,核心算法等。 +运用了单链表和队列,运用了排序,插入和删除的算法。 确定解决问题的总体思路和方案。 +客户排队的过程是一个按照到达时间先到先接受服务的过程,这一过程可以通过队列实现。n个不同的窗口对应n个队列,队列中每一个元素对应一个客户。算法中处理的事件有两类:一类是客户到来事件,另一类是客户离开事件。客户到来事件发生的时刻随客户到来自然形成,客户到来后,使用排序算法找到最短的等待队列,将客户插入队列;客户离开事件发生的时刻由银行窗口为其办理业务的完成时间决定,业务办理完成后,客户从等待队列中出队,窗口为下一个客户(如果存在的话)服务。 +本项目涉及的对象主要包括客户、银行窗口,可以设计两个数据结构实现客户和窗口的功能。此外,管理客户、队列的功能可以单独实现(类似银行进门处的接待员,引导客户在哪里排队)。 ## 1.3 需求分析 @@ -140,7 +145,6 @@ void bubble_sort(T a[], int n) # 参考文献 -列出参考的文献资料,根据情况自行添加。 [1] 严蔚敏, 吴伟民. 数据结构(C语言版). 北京: 清华大学出版社, 2007. diff --git a/vscode.cpp b/vscode.cpp new file mode 100644 index 0000000..ff14ede --- /dev/null +++ b/vscode.cpp @@ -0,0 +1,372 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#include +#define TRUE 1 +#define FALSE 0 +#define OK 1 +#define ERROR 0 +#define INFEASIBLE -1 +#define OVERFLOW -2 +typedef int Status; +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; +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  + +/* +系统每次随机生成的是 +1)当前顾客顾客的柜台被服务时间 +2)当前顾客和下一个顾客到达的间隔时间 +记住这2点就便于理解整个代码 +每个顾客在银行的等待时间取决于队列里前一个节点的离开时间,而不是自己的到达时间+服务时间。*/ + +int main() +{ +srand((unsigned)time(NULL));//设定随机数种子 +printf("请输入银行的营业时间(min):"); +scanf("%d", &CloseTime); +Bank_Simulation(CloseTime); +return 0; +} +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; +} diff --git a/vscode.exe b/vscode.exe new file mode 100644 index 0000000..5c98b10 Binary files /dev/null and b/vscode.exe differ