PTA习题解析——银行排队问题
前言
生活处处有排队,在食堂买饭、在医院挂号、在超市等待结账。有些时候,排队问题会略显复杂,例如在银行等待办理业务,窗口很多,你要在哪个窗口办理?有的窗口看似人少,但是办理的时间很长,你要做何选择?排队的时候会不会出现插队、一个人办理多个业务的情况呢?现在我们需要利用数据结构的知识,通过编程来模拟银行排队的情景。
银行排队问题之单队列多窗口服务
题干
输入样例
9
0 20
1 15
1 61
2 10
10 5
10 3
30 18
31 25
31 2
3
输出样例
6.2 17 61
5 3 1
题干划重点
题设 | 解析 |
---|---|
顾客按到达时间在黄线后排成一条长龙 | 考虑用队列组织数据 |
当有窗口空闲时,下一位顾客即去该窗口处理事务 | 不会有插队情况 |
情景模拟
我们使用题目提供的样例数据,利用一些图形来进行模拟一遍排队的流程。我假设界面的左边是客户排队队列,中间是银行办理的窗口,根据题设我们需要统计每一个窗口的3个参数,分别是 time ——窗口恢复空闲的时间(办理时间)、amount ——办理的客户数量、wait_time ——客户在该窗口等待的总时间(等待时间),为了使数据更为直观,我们将把每一位顾客办理结束后的各个参数都保留下来。
首先是在队列头的顾客出等待队列,由于窗口全部空闲,因此该顾客进入第一个窗口办理。由于不需要等待,因此只需要更新办理时间和客户数量。界面改变如下:
接着,第二位客户出队列,由于还有空闲窗口,因此顾客只需要按顺序进入下一个空闲的窗口。
第三位顾客同第二位。
第四位顾客办理的时候,请注意看,此时已经没有空闲的窗口了,因此我们需要找到最先恢复空闲的窗口,安排客户进入。经过比较,我们能很直观地看见是第二个窗口最先恢复,此时该窗口的等待时间也要进行更新,窗口恢复空闲在 16 时刻,客户在 2 时刻到来,因此等待时间更新为 14。
第五位客户同第四位。
第六位客户同第五位。
第七位客户请注意看,当这位顾客到来的时候,窗口二已经恢复空闲,因此在更新窗口二的办理时间时,需要先用客户的到来时间先更新一次。
第八位客户同第七位。
第九位客户同第六位。
到此为止,我们把所有客户安排得明明白白,我们得到了一组数据。根据这些数据,我们发现问题已经得到解决了,除了单个客户最大等待时间,我们可以在模拟的时候实时记录,其他数据我们都准备好了。
数据结构选择
由于不需要考虑插队问题,客户按照先来先到的顺序来办理,因此对于排队队列可以用一个队列结构来描述,我选择使用 STL 库的 queue 容器来组织数据。对于单个客户我们需要记录到来时间和办理消耗时间,因此可以定义一个结构体来组织。对于窗口而言,由于我们需要来遍历每个窗口,每个窗口具有相同的参数,因此可以定义一个结构体来组织单个窗口的参数,使用 STL 库的 vector 容器来描述多个窗口。
程序流程
代码实现
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
typedef struct customer
{
int come;
int use;
}customer;
typedef struct window
{
int time;
int amount;
int wait_time;
}window;
int main()
{
int people_num;
int i, flag;
int windows_num;
int min_time, num, max_waittime(0), maxtime(0); //最快可办理窗口时间、最快可办理窗口编号、最大等待时间、最后办理结束时间
double avg(0);
window a_window; //用于制造单个窗口
customer people; //存储单个客户信息
queue<customer> wait; //客户等待队列
vector<window> windows; //模拟 n 个窗口
cin >> people_num;
for (i = 0; i < people_num; i++)
{
cin >> people.come >> people.use;
if (people.use > 60)
{
people.use = 60;
}
wait.push(people);
}
cin >> windows_num;
a_window.amount = 0;
a_window.time = 0;
a_window.wait_time = 0;
for ( i = 0; i < windows_num; i++) //制造 n 个窗口
{
windows.push_back(a_window);
}
while (!wait.empty()) //等待队列不为空
{
flag = 0;
min_time = 9999;
people = wait.front();
wait.pop();
for ( i = 0; i < windows_num; i++)
{
if (windows[i].time <= people.come) //有空闲窗口
{
windows[i].time = people.come;
windows[i].time += people.use;
windows[i].amount++;
flag++;
break;
}
if (windows[i].time < min_time) //记录最先可办理窗口
{
num = i;
min_time = windows[i].time;
}
}
if (flag == 1) //已经在空闲窗口办理
{
continue;
}
if (people.come > windows[num].time) //到最先可办理窗口办理
{
windows[num].time = people.come;
}
else
{
windows[num].wait_time += (windows[num].time - people.come);
if ((windows[num].time - people.come) > max_waittime) //更新最大等待时间
{
max_waittime = windows[num].time - people.come;
}
}
windows[num].time += people.use;
windows[num].amount++;
}
for ( i = 0; i < windows_num; i++)
{
avg += windows[i].wait_time;
if (windows[i].time > maxtime)
{
maxtime = windows[i].time;
}
}
avg = 1.0 * avg / people_num;
printf("%.1f ", avg);
cout << max_waittime << " " << maxtime << endl;
cout << windows[0].amount;
for ( i = 1; i < windows_num; i++)
{
cout << " " << windows[i].amount;
}
return 0;
}
银行排队问题之单窗口“夹塞”版
题干
输入样例
6 2
3 ANN BOB JOE
2 JIM ZOE
JIM 0 20
BOB 0 15
ANN 0 30
AMY 0 2
ZOE 1 61
JOE 3 10
输出样例
JIM
ZOE
BOB
ANN
JOE
AMY
75.2
题干划重点
题设 | 解析 |
---|---|
假设所有人到达银行时,若没有空窗口,都会请求排在最前面的朋友帮忙 | 有插队现象 |
情景模拟
我们使用题目提供的样例数据,利用一些图形来进行模拟一遍排队的流程。我假设界面的左边是客户排队队列,中间是银行办理的窗口,根据题只需要考虑一个窗口的情况,因此只需要记录恢复空闲时间(办理时间)、客户总等待时间(等待时间),为了使数据更为直观,我们将测试样例给的交际圈也通过图形进行表示。
首先排在队首的 JIM 先进入窗口办理,更新窗口的办理时间,由于 JIM 有交际圈,且他的朋友已经在 JIM 办理结束之前来到等待队列了,因此安排 ZOE 进入窗口插队,然后 JIM 出队列。
接下来由插队的 JIM 办理,更新办理时间和等待时间。由于 ZOE 的交际圈中已经没有其他需要插队的朋友了,因此不发生插队。
由于没发生插队,按照排队队列的顺序 BOB 进入窗口,由于 BOB 有交际圈,且他的朋友已经在 BOB 办理结束之前来到等待队列了,因此安排 ANN 进入窗口插队,然后 BOB 出队列。
接下来同上一步,ANN 出队列,JOE 插队。
由于 JOE 的交际圈中已经没有其他需要插队的朋友了,因此不发生插队。
最后 AMY 进入队列办理,更新数据,模拟结束。
数据结构选择
由于需要考虑插队问题,因此我选择使用 STL 库的 vector 容器来组织数据。对于单个客户我们需要记录到来时间和办理消耗时间,因此可以定义一个结构体来组织,为了描述插队的问题,引入一个 int 类型的成员变量来表示客户是否已办理。对于窗口而言,由于我们只需要描述一个窗口,因此我选择使用 STL 库的 queue 容器来描述。
程序流程
代码实现
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <map>
using namespace std;
typedef struct customer
{
string name;
int come;
int use;
int state; //对付夹塞的情况,是否办理的 flag
}customer;
int main()
{
int num;
int i, flag, idx(0);
int wait_time, now;
customer a_person; //保存单个客户信息
string name; //保存单个客户信息
vector<customer> people; //等待队列
map<string, int> circle; //交际圈字典
queue<customer> run; //模拟单窗口
double avg;
cin >> num >> now;
for (i = 0; i < now; i++)
{
cin >> wait_time;
while (wait_time--)
{
cin >> name;
circle[name] = i;
}
}
for ( i = 0; i < num; i++)
{
cin >> a_person.name >> a_person.come >> a_person.use;
if (a_person.use > 60)
{
a_person.use = 60;
}
a_person.state = 0;
people.push_back(a_person);
}
run.push(people[0]); //第一位客户从等待队列进入模拟窗口
now = people[0].come + people[0].use; //更新办理时间
wait_time = 0; //初始化等待时间
while(!run.empty())
{
cout << run.front().name << endl;
flag = 0;
for ( i = idx + 1; i < num; i++) //寻找窗口前的客户需不需要夹塞
{
if (people[i].come > now) //当前时间后面的客户还没来
{
break;
}
if (circle.find(people[i].name) != circle.end() && people[i].state != 1 && circle[run.front().name] == circle[people[i].name])
{
wait_time += (now - people[i].come); //更新等待时间
now += people[i].use; //更新办理时间,顺序不能颠倒
run.push(people[i]); //插队入窗口
people[i].state = 1; //修改夹塞顾客已办理
flag = 1;
break;
}
}
run.pop(); //窗口前顾客结束办理
if (flag == 1) //窗口前仍然有顾客
{
continue;
}
for ( i = idx + 1; i < num; i++) //窗口前没有顾客了,即没出现夹塞
{
if (people[i].state != 1) //按等待队列顺序安排下一位顾客到窗口办理
{
idx = i;
if ((now - people[i].come) > 0) //更新等待时间
{
wait_time += (now - people[i].come);
}
if (people[i].come > now) //更新办理时间
{
now = people[i].come;
}
now += people[i].use;
people[i].state = 1;
run.push(people[i]);
break;
}
}
}
avg = 1.0 * wait_time / num;
printf("%.1f", avg);
return 0;
}
银行排队问题之单队列多窗口加VIP服务
题干
测试样例
10
0 20 0
0 20 0
1 68 1
1 12 1
2 15 0
2 10 0
3 15 1
10 12 1
30 15 0
62 5 1
3 1
输出样例
15.1 35 67
4 5 1
题干划重点
题设 | 解析 |
---|---|
当该窗口空闲并且队列中有VIP客户在等待时,排在最前面的VIP客户享受该窗口的服务 | 有插队现象,仅发生于 vip 客户 |
否则一定选择VIP窗口 | vip 需要优先判断是否进 vip 窗口 |
数据结构选择
由于需要考虑插队问题,因此我选择使用 STL 库的 vector 容器来组织数据。对于单个客户我们需要记录到来时间和办理消耗时间,因此可以定义一个结构体来组织,为了描述插队的问题,引入一个 int 类型的成员变量来表示客户是否已办理,为了描述一个客户是否是 vip,因此引入一个 int 类型的成员变量来表示客户是否是 vip。对于窗口而言,由于我们需要来遍历每个窗口,每个窗口具有相同的参数,因此可以定义一个结构体来组织单个窗口的参数,使用 STL 库的 vector 容器来描述多个窗口。由于 vip 窗口需要特殊处理,因此需要有一个下标来保存 vip 窗口的编号。
描述插队操作,由于我描述排队队列使用 vector 容器,我的做法是先用一个变量保存按照顺序应该进行办理的客户的编号,然后把这个编号先改为插队的 vip 编号,然后等 vip 完成办理之后再将编号改回来。这么做的好处是,可以不用另外写代码专门处理 vip 客户,让 vip 客户使用普通客户办理的代码来操作。
程序流程
这道题目的思想就是上述两题的一个结合,即考虑插队的多窗口问题,因此不再进行模拟,如果你仍然有疑惑,可以仿照上文动笔进行模拟,以此理清思路。
代码实现
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
typedef struct customer
{
int come;
int use;
int vip;
int state;
}customer;
typedef struct window
{
int time;
int amount;
int wait_time;
}window;
int main()
{
int people_num;
int i, j, k, flag, flag2, temp;
int windows_num, w_vip;
int min_time, num, max_waittime(0), maxtime(0);
double avg(0);
window a_window;
customer people;
vector<customer> wait;
vector<window> windows;
cin >> people_num;
for (i = 0; i < people_num; i++)
{
cin >> people.come >> people.use >> people.vip;
if (people.use > 60)
{
people.use = 60;
}
people.state = 0;
wait.push_back(people);
}
cin >> windows_num >> w_vip;
a_window.amount = 0;
a_window.time = 0;
a_window.wait_time = 0;
for (i = 0; i < windows_num; i++)
{
windows.push_back(a_window);
}
for ( i = 0; i < people_num; i++)
{
if (wait[i].state == 1)
{
continue;
}
min_time = flag = flag2 = 0;
if (wait[i].vip == 1) //是 vip
{
if (windows[w_vip].time <= wait[i].come) //vip窗口是否空闲
{
windows[w_vip].time = wait[i].come + wait[i].use;
windows[w_vip].amount++;
}
else //寻找最快窗口
{
min_time = w_vip;
for ( j = 0; j < windows_num; j++)
{
if (windows[j].time < windows[min_time].time)
{
min_time = j;
}
}
if (windows[min_time].time <= wait[i].come) //不用等
{
windows[min_time].time = wait[i].come + wait[i].use;
}
else //要等
{
windows[min_time].wait_time += (windows[min_time].time - wait[i].come);
if ((windows[min_time].time - wait[i].come) > max_waittime)
{
max_waittime = (windows[min_time].time - wait[i].come);
}
windows[min_time].time += wait[i].use;
}
windows[min_time].amount++;
}
wait[i].state = 1;
}
else //不是vip
{
for (j = 0; j < windows_num; j++) //找空闲窗口
{
if (windows[j].time < wait[i].come)
{
//空闲窗口是 vip
if (j == w_vip)
{
for ( k = i + 1; k < people_num && wait[k].come < windows[j].time ; k++) //加塞
{
if (wait[k].vip == 1 && wait[k].state == 0)
{
temp = i;
i = k;
flag2++;
break;
}
}
}
//空闲窗口是 vip
windows[j].time = wait[i].come + wait[i].use;
windows[j].amount++;
wait[i].state = 1;
flag++;
break;
}
if (windows[j].time < windows[min_time].time)
{
min_time = j;
}
}
if (flag == 0)
{
//最快窗口是 vip
if (min_time == w_vip)
{
for (k = i + 1; k < people_num ; k++) //加塞
{
if (wait[k].come > windows[min_time].time)
{
break;
}
if (wait[k].vip == 1 && wait[k].state == 0)
{
temp = i;
i = k;
flag2++;
break;
}
}
}
//最快窗口是 vip
if (windows[min_time].time <= wait[i].come) //不用等
{
windows[min_time].time = wait[i].come + wait[i].use;
}
else //要等
{
windows[min_time].wait_time += (windows[min_time].time - wait[i].come);
if ((windows[min_time].time - wait[i].come) > max_waittime)
{
max_waittime = (windows[min_time].time - wait[i].come);
}
windows[min_time].time += wait[i].use;
}
windows[min_time].amount++;
wait[i].state = 1;
}
if (flag2 == 1)
{
i = temp;
i--;
}
}
/*cout << endl;
for (int k = 0; k < windows_num; k++)
{
cout << windows[k].time << " ";
}
cout << endl;
for (k = 0; k < windows_num; k++)
{
cout << windows[k].amount << " ";
}
cout << endl;
for (k = 0; k < windows_num; k++)
{
cout << windows[k].wait_time << " ";
}
cout << endl;*/
}
for (i = 0; i < windows_num; i++)
{
avg += windows[i].wait_time;
if (windows[i].time > maxtime)
{
maxtime = windows[i].time;
}
}
avg = 1.0 * avg / people_num;
printf("%.1f ", avg);
cout << max_waittime << " " << maxtime << endl;
cout << windows[0].amount;
for (i = 1; i < windows_num; i++)
{
cout << " " << windows[i].amount;
}
return 0;
}