前言
生活處處有排隊,在食堂買飯、在醫院掛號、在超市等待結賬。有些時候,排隊問題會略顯復雜,例如在銀行等待辦理業務,窗口很多,你要在哪個窗口辦理?有的窗口看似人少,但是辦理的時間很長,你要做何選擇?排隊的時候會不會出現插隊、一個人辦理多個業務的情況呢?現在我們需要利用數據結構的知識,通過編程來模擬銀行排隊的情景。

銀行排隊問題之單隊列多窗口服務
題干

輸入樣例
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;
}
