|
|
|
@ -85,29 +85,56 @@ durtime客户办理业务的时间;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 2.1 概要设计
|
|
|
|
|
主程序的流程\n主程序先是让外部进行测试数据输入,待测试数据输入完后,执行银行业务模拟系统,产生需要取款的客户人数,成功办理存款的客户人数,成功办理取款的客户人数,存款成功办理率,取款成功办理率,客户逗留平均时间,银行当前余额等信息。
|
|
|
|
|
本设计中用到的数据结构ADT定义如下:
|
|
|
|
|
#### 主程序的流程
|
|
|
|
|
主程序先是让外部进行测试数据输入,待测试数据输入完后,执行银行业务模拟系统,产生客户逗留平均时间。
|
|
|
|
|
###### 其具体操作如下:
|
|
|
|
|
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]);//求长度最短队列
|
|
|
|
|
````
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
ADT Queue{
|
|
|
|
|
数据对象:D={ ai | ai∈ElemSet, i=1,2,...,n, n≥0 }
|
|
|
|
|
数据关系:R1={ <ai-1, ai>|ai-1, ai∈D, i=2,...,n }
|
|
|
|
|
基本操作:
|
|
|
|
|
void InitQueue(Queue &Q);
|
|
|
|
|
操作结果:构造空队列Q
|
|
|
|
|
CustNode *Queuefront(Queue &Q);
|
|
|
|
|
初始条件:队列Q存在
|
|
|
|
|
操作结果:返回队首元素
|
|
|
|
|
CustNode *Queuerear(Queue &Q);
|
|
|
|
|
初始条件:队列Q存在
|
|
|
|
|
操作结果:返回队尾元素
|
|
|
|
|
void EnQueue(Queue &Q,int e);
|
|
|
|
|
初始条件:队列Q存在
|
|
|
|
|
操作结果:插入元素e为Q的新的队尾元素。
|
|
|
|
|
void DeQueue(Queue &Q);
|
|
|
|
|
初始条件:队列Q存在
|
|
|
|
|
操作结果:删除Q的队头元素。
|
|
|
|
|
}ADT Queue
|
|
|
|
|
(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 数据结构设计
|
|
|
|
|
运用链队列、链表的数据结构,使银行业务的模拟更加高效的完成。链表和链队列的结构,更加形象的体现了客户的在银行的排队、业务办理、离开的流程。
|
|
|
|
@ -176,38 +203,235 @@ IF 最短长度>Q[j]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 3. 系统实现
|
|
|
|
|
说明所使用的语言、开发工具等。
|
|
|
|
|
介绍项目的文件结构,以及主要函数的功能。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
语言:C语言
|
|
|
|
|
开发工具:VS Code
|
|
|
|
|
采用模块化设计,通过大量的函数实现,银行开门、客户进门、客户离开等功能进而统计一天内客户在银行逗留的平均时间
|
|
|
|
|
## 3.1 核心数据结构的实现
|
|
|
|
|
描述数据结构的实现方法。
|
|
|
|
|
可以配合程序代码加以说明。如:
|
|
|
|
|
首先通过有序链表,实现一部分功能如:先初始化有序表,在第i个位置之前插入元素e,删除链表中第一个结点并以q返回,已知p指向线性链表中的一个结点,返回p所指结点中元素的值等等。其次通过队列实现入队和出队。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
struct LNode {
|
|
|
|
|
E data; // 数据元素
|
|
|
|
|
LNode *next; // 指向下一个结点的指针
|
|
|
|
|
};
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
## 3.2 核心算法的实现
|
|
|
|
|
描述算法的实现方法。
|
|
|
|
|
可以配合程序代码加以说明。如:
|
|
|
|
|
while (p != NULL) {
|
|
|
|
|
printf("OccurTime:%d,Event Type:%d\n", p->data.OccurTime, p->data.NType);
|
|
|
|
|
p = p->next;
|
|
|
|
|
}
|
|
|
|
|
printf("\n");
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
// 冒泡排序
|
|
|
|
|
void bubble_sort(T a[], int n)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.系统测试
|
|
|
|
@ -358,8 +582,9 @@ The Average Time is 15.285714
|
|
|
|
|
成员(1)刘彩月:
|
|
|
|
|
经过这次数据结构的实训,我认识到了数据结构和生活应用的联系。用数据结构中的队列和单链表的知识,可以模拟解决银行的排队问题。同时我也感受到了个人技术和团队合作的重要性,在学习数据结构的同时,我应该联合生活中的一些具体的问题,联系具体问题来巩固数据结构的相关知识;通过数据结构解决生活中的相关问题。努力提高自己的个人技术,提高自己的编程能力。良好的团队合作往往可以达到事半功倍的效果,我还应该注重团队合作的重要性。和队员之间相互合作,相互信任等都是团队合作中最基础的。
|
|
|
|
|
成员(2)金雨佳:
|
|
|
|
|
成员(3)赵亚璇:
|
|
|
|
|
成员(3)赵亚璇:在项目的前期准备阶段,大家一起讨论,跟随课程的推进,使用课程中学到的各种知识,将整个项目的架构逐渐搭建起来,层次和逻辑也越来越清晰。在这一阶段时间,学习的过程虽然不容易,但确实学到了不少实用的知识。在运用相关框架进行实践开发的过程中,也遇到了各种各样的问题,通过查找资料和不断的尝试也都逐一解决了,这一过程自己的能力也得到了锻炼,有不错的成就感。
|
|
|
|
|
成员(4)刘梦琦:
|
|
|
|
|
通过这次数据结构实训,让我意识到了团队协作的重要性,运用数据结构的知识模拟银行系统,更加熟练地掌握单链表和队列的应用,将所学的知识用在实践项目上时我此次实训的最大收获,在未来的生活中,我要努力学习技术,将知识联系到实际生活当中,把所学用到所用。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 参考文献
|
|
|
|
|