Data Structure第四次作業講解
寫給讀者的話(務必閱讀)
期中以來,有不少同學向我反應代碼寫的慢、正確率不高等問題。由於OS已經爆炸閑的沒事干 因此我決定將自己原來寫的代碼重構重構,並整理成博客附上整個思路的講解。首先,我必須申明,博客所寫的東西,是供想提升自己代碼水平,理清寫碼思路的同學用的。我希望同學們能夠明白:作為一個考試分數占 80% 的學科,抄襲他人代碼完成作業不是白賺了那 20% 的分數,而是失去了一次良好的練兵的機會。其次,個人所寫代碼只是提供一個寫碼、思考問題的思路,並不代表題目的唯一解法,更不代表最優解法沒有提交到課程網站上測試,只在本地通過測試數據,甚至可能有bug噢。我希望我的代碼能夠起到拋磚引玉的作用,能夠讓同學對課上內容有更深刻的理解,寫代碼時能夠有更多更好的想法。最后,我希望同學們在完成作業的同時,能夠對自己的代碼進行復雜度的分析。數據結構的使用,往往離不開對性能的約束,因此,掌握復雜度的分析也是這門課程重要的一環。
關於代碼風格
本文中所有的代碼風格皆采取 OO 的標准,同時作者也希望同學們能夠以這種標准約束自己,這樣也會方便助教 debug。簡單來說,大致約束如下:
1、符號后帶空格。
2、大括號不換行。
3、if、while、for 等括號兩端應該帶空格,並且一定要用大括號括起來。
4、一行不要寫過多字符(不超過60),較長的判斷可以換行處理。
5、縮進為 4 個空格,不同層次間要有合適的縮進。
6、一行只聲明一個變量,只執行一個語句。
第一題:棧操作(基本題)
題目描述
【問題描述】
假設給定的整數棧初始狀態為空,棧的最大容量為100。從標准輸入中輸入一組棧操作,按操作順序輸出出棧元素序列。棧操作:1表示入棧操作,后跟一個整數(不為1、0和-1)為入棧元素;0表示出棧操作;-1表示操作結束。
【輸入形式】
從標准輸入讀取一組棧操作,入棧的整數和表示棧操作的整數之間都以一個空格分隔。
【輸出形式】
在一行上按照操作的順序輸出出棧元素序列,以一個空格分隔各元素,最后一個元素后也要有一個空格。如果棧狀態為空時進行出棧操作,或棧滿時進行入棧操作,則輸出字符串“error”,並且字符串后也要有一空格。所有操作都執行完后,棧也有可能不為空。
【樣例輸入】
1 3 1 5 1 7 0 0 1 8 0 1 12 1 13 0 0 0 0 1 90 1 89 0 -1
【樣例輸出】
7 5 8 13 12 3 error 89
【樣例說明】
入棧元素依次為3、5、7,然后有兩次出棧動作,所以先輸出7和5,這時棧中只有元素3;之后元素8入棧,又出棧,輸出8;隨后元素12和13入棧,再進行4次出棧操作,輸出13、12和3,這時棧為空,再進行出棧操作會輸出error;最后90和89入棧,進行一次出棧操作,輸出89,棧中剩余1個元素。
題目大意
一眼題,一看就是課上講的棧的模擬。
題目思路
就實現個棧,簡單點就直接用數組模擬,復雜點就鏈表維護。
但是,就算是這么簡單的一個問題,我們也應該清晰的認識到,棧是我們使用的工具,而其入棧出棧是兩個分離的操作,我們應該分別對其實現函數,而不是擠在 main 里。
代碼實現
#include <stdio.h>
#include <ctype.h>
inline int read() { //快速讀入,可以放在自己的缺省源里面
int x = 0; //數字位
int f = 1; //符號位
char ch = getchar(); //讀入第一個字符
while (!isdigit(ch)) { //不是數字
if (ch == '-') { //特判負號
f = -1;
}
ch = getchar();
}
while (isdigit(ch)) { //讀入連續數字
x = (x << 3) + (x << 1) + ch - '0'; // x * 10 == (x << 3) + (x << 1)
ch = getchar();
}
return x * f;
}
#define maxn 105
int sta[maxn]; //越簡單越好,必然是用數組實現棧
int top;
void push(int x) {//入棧
if (top == 100) { //棧滿
printf("error ");
return;
}
sta[++top] = x;
}
void pop() { //出棧並輸出
if (top == 0) { //棧空
printf("error ");
return;
}
printf("%d ", sta[top--]);
}
int main() {
int ty;
while (1) {
ty = read();
if (ty == -1) { //結束條件
break;
}
if (ty == 1) {
int x = read();
push(x);
}
if (ty == 0) {
pop();
}
}
return 0;
}
復雜度分析
典型的棧模擬題,每個元素只會入棧一次,出棧一次,因此,復雜度是 O(n) 的。
第二題:C程序括號匹配檢查
題目描述
【問題描述】
編寫一程序檢查C源程序文件中{}、()等括號是否匹配,並輸出第一個檢測到的不匹配的括號及所對應括號所在的行號(程序中只有一個括號不匹配)。
注意:
1.除了括號可能不匹配外,輸入的C源程序無其它語法錯誤。
2.字符常量、字符串常量及注釋中括號不應被處理,注釋包括單行注釋//和多行/* */注釋
3.字符常量和字符串常量中不包含轉義字符'和";
4.程序中出現有意義括號的個數不超過200個;
不匹配判斷規則:
1.當檢測的程序括號為'{'時,若其前序尚未匹配的括號為'('時,輸出該'('左括號及所在行號;
2.當遇到一個不匹配的右括號')'或'}'時,輸出該右括號及所在行號;
3.當程序處理完畢時,還存在不匹配的左括號時,輸出該左括號及所在行號。
【輸入形式】
打開當前目錄下文件example.c,查詢其括號是否匹配。該文件中每行字符數不超過200。
【輸出形式】
若存在括號不匹配時,應輸出首先能判斷出現不匹配的括號及其所在的行號。當出現括號不匹配時,按下面要求輸出相關信息:
without maching
其中
若整個程序括號匹配,則按下面所示順序輸出括號匹配情況,中間沒有空格。
(){(()){}}
題目大意
讓我們對一個 C 代碼做括號匹配,匹配成功的話還要輸出匹配的過程。
需要注意的是代碼中存在注釋,因此我們不能簡單的處理所有的長得像注釋的字符,注意的第三點也點明了引號的存在,不在字符串常量中出現轉義的引號可以說是極大的簡化了我們的問題。
題目思路
依然是十分典型的棧問題,我們需要對括號做匹配。
而注釋的處理我們可以這樣理解:我們當前讀到的代碼有兩種狀態:注釋態和非注釋態,我們可以通過一些巧妙的處理,合理地在這兩種狀態之間轉換,而括號匹配只在注釋態中進行就可以了。
代碼實現
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#define maxn 100005
char s[maxn]; //臨時儲存地字符串數組
char ans[maxn]; //用於存儲已經匹配的括號序列
int pnum; //已經匹配的數量
int lineNum[maxn]; //記錄棧中括號所在的行數
int sta[maxn]; //括號棧
int top; //棧頂
int ban; //當前是否有效的標記,無效時同時記錄無效的類型
char fType[6]; //括號類型到括號的反向映射
int getType(char c) { // 返回括號的類型
if (c == '(') return 2;
if (c == ')') return 3;
if (c == '{') return 4;
if (c == '}') return 5;
return 0;
}
void pre() { //初始化反向映射
fType[2] = '(';
fType[3] = ')';
fType[4] = '{';
fType[5] = '}';
}
int tryPush(int x, int line) {
if (top && sta[top] < x) { //當前存在棧頂,並且棧頂的括號大小關系錯誤
printf("without maching \'%c\' at line %d\n", fType[sta[top]], lineNum[top]);
return -1;
}
sta[++top] = x; //壓入棧頂
lineNum[top] = line; //記錄行數
return 0;
}
int tryPop(int x, int line) {
if (!top || (sta[top] ^ 1) != x) { //檢查括號是否匹配
printf("without maching \'%c\' at line %d\n", fType[x], line);
return -1;
}
--top;
return 0;
}
void changeBan(int* pos) { //特判更改當前的注釋狀態
if (!ban) {
if (s[*pos] == '\"') {
ban = 1;
return;
}
if (s[*pos] == '\'') {
ban = 2;
return;
}
if (s[*pos] == '/' && s[(*pos) + 1] == '*') {
ban = 3;
++(*pos);
return;
}
if (s[*pos] == '/' && s[(*pos) + 1] == '/') {
ban = 4;
++(*pos);
return;
}
} else {
if (ban == 1 && s[*pos] == '\"') {
ban = 0;
return;
}
if (ban == 2 && s[*pos] == '\'') {
ban = 0;
return;
}
if (ban == 3 && s[*pos] == '*' && s[(*pos) + 1] == '/') {
ban = 0;
++(*pos);
return;
}
}
}
int main() {
pre();
FILE* IN = fopen("example.c", "r");
int line = 0; //記錄當前行數
while (fgets(s, maxn - 5, IN) != NULL) { //每次處理一行
++line;
int len = strlen(s);
int i;
int temp;
for (i = 0; i < len; ++i) {
if (!ban && (temp = getType(s[i]))) {
if (temp & 1) {
if (tryPop(temp, line) < 0) {
return 0;
}
} else {
if (tryPush(temp, line) < 0) {
return 0;
}
}
ans[++pnum] = s[i]; //直接記錄有效括號
} else {
changeBan(&i);
}
}
if (ban == 4) { //當前行結束,去除//所產生的注釋狀態
ban = 0;
}
}
int i;
if (top) {
printf("without maching \'%c\' at line %d\n", fType[sta[top]], lineNum[top]);
} else {
for (i = 1; i <= pnum; ++i) {
printf("%c", ans[i]);
}
}
return 0;
}
相信不少同學就算看完注釋還是一頭霧水,下面我們就將這個代碼分解一下,解釋一下映射在簡化代碼之中的作用。
我們將 '(' 、')' 、'{'、'}' 分別映射為 2、3、4、5,通過數學的性質方便了我們的處理。
2 ^ 1 == 3、3 ^ 1 == 2,通過這種映射我們能夠通過一個異或操作判斷括號是否是正確匹配的。
同時,由於括號的大小關系我們用數字表示了出來,因此在壓棧的時候,我們應該做的是一個單調不增的棧,否則說明括號的包含關系產生了錯誤。
之后,注釋這種極其特殊的操作我們就只能通過繁雜的特判來進行了。
復雜度分析
每個字符顯然只被處理了一次,復雜度是 O(|S|) 的。
第三題:計算器(表達式計算-后綴表達式實現)
題目描述
【問題描述】
從標准輸入中讀入一個整數算術運算表達式,如24 / ( 1 + 5%3 + 36 / 6 / 2 - 2) * ( 12 / 2 / 2 )= ,計算表達式結果,並輸出。
要求:
1、表達式運算符只有+、-、*、/、%,表達式末尾的=字符表示表達式輸入結束,表達式中可能會出現空格;
2、表達式中會出現圓括號,括號可能嵌套,不會出現錯誤的表達式;
3、出現除號/時,以整數相除進行運算,結果仍為整數,例如:5/3結果應為1。
4、要求采用逆波蘭表達式來實現表達式計算。
【輸入形式】
從鍵盤輸入一個以=結尾的整數算術運算表達式。操作符和操作數之間可以有空格分隔。
【輸出形式】
在屏幕上輸出計算結果(為整數,即在計算過程中除法為整除)。
【樣例輸入】
24 / ( 1 + 5%3 + 36 / 6 / 2 - 2) * ( 12 / 2 / 2 ) =
【樣例輸出】
18
【樣例說明】
按照運算符及括號優先級依次計算表達式的值。
【評分標准】
通過所有測試點得滿分。
題目大意
表達式計算,要求用后綴表達式來實現。
題目思路
既然是要求轉換為后綴表達式再計算,我們就按生成后綴表達式的步驟做就好了。
1、依次讀取輸入的表達式,如果是操作數,則把它放入到輸出中。
2、如果是操作符,棧為空的話直接將該操作符入棧;如果棧非空,則比較棧頂操作符和該操作符優先級,如果棧頂操作符優先級小於該操作符,則該操作符入棧;否則彈出棧頂操作符並將其放入到輸出中,直到棧為空或者發現優先級更低的操作符為止。
3、如果是括號,比如'('和')',則特殊處理。如果是'('的話,直接入棧;如果是')',那么就將棧頂操作符彈出寫入到輸出中,直到遇到一個對應的'(',但是這個'('只彈出不寫入到輸出中。注意:"("可以理解為優先級最高。
4、當表達式讀取完畢后,如果棧中還有操作符,則依次彈出操作符並寫入到輸出中。
而計算后綴表達式的步驟則更加簡單:
1、是數字,直接壓入棧
2、是符號,取出棧頂的兩個值計算后成為新棧頂。
代碼實現
#include<stdio.h>
#include<ctype.h>
#include<string.h>
#define maxn 1000005
int len; //記錄字符串長度
int top; //記錄棧頂
char s[maxn]; //讀入並轉換的表達式
char sta[maxn]; //當前的符號棧
int pri[256]; //定義符號的優先級
void getSuf() { // 讀入並將表達式轉換為后綴形式
char c;
pri['+'] = pri['-'] = 1;
pri['*'] = pri['/'] = pri['%'] = 2;
while (1) {
c = getchar();
if (isdigit(c)) {
while(isdigit(c)) {
s[len++] = c;
c = getchar();
}
s[len++] = ' '; // 后綴表達式數字可能相連,因此添加空格避免數字連續。
}
if (c == '=') {
break;
}
if (pri[c]) { //說明是一個符號,需要與棧中符號優先級進行比較
while (top && pri[sta[top]] && pri[sta[top]] >= pri[c]) {//注意 () 優先級為 0
s[len++] = sta[top--];
}
sta[++top] = c;
}
if (c == '(') {
sta[++top] = c;
}
if (c == ')') { //需要將 ( 之前所有符號彈出
while(sta[top] != '(') {
s[len++] = sta[top--];
}
--top;
}
}
while (top) {
s[len++] = sta[top--];
}
}
int numSta[maxn]; //數字棧
int numTop; //數字棧棧頂
int calc(int x, char c, int y) {
if (c == '+') {
return x + y;
}
if (c == '-') {
return x - y;
}
if (c == '*') {
return x * y;
}
if (c == '/') {
return x / y;
}
if (c == '%') {
return x % y;
}
}
void calcSuf() {
int i = 0;
while (i < len) {
if (isdigit(s[i])) { //是數字,准備壓入數字棧
int x = s[i++] - '0';
while(isdigit(s[i])) {
x = (x << 3) + (x << 1) + s[i++] - '0';
}
numSta[++numTop] = x;
}
if (pri[s[i]]) { //是符號,對棧頂兩個數字計算得到新棧頂
int a = numSta[numTop--];
int b = numSta[numTop--];
numSta[++numTop] = calc(b, s[i], a);
}
++i;
}
printf("%d\n", numSta[numTop]);
}
int main() {
getSuf();
calcSuf();
return 0;
}
這個題我們同樣采取了類似上題映射的方法,將每個計算符號映射到自己的優先級,並通過這種方式來得到一種統一的處理方式。
復雜度分析
每個字符被處理了兩次,復雜度依然是 O(|S|) 的。
第四題:文本編輯操作模擬(簡)
題目描述
【問題描述】
編寫一程序模擬文本編輯操作。首先從標准輸入讀取一行字符串(字符個數不超過512),該行字符串是已經過n(大於0,小於等於10)步編輯操作后的結果。然后從下一行讀取n,以及已發生過的n步編輯操作,編輯操作分行輸入,輸入格式為:
op pos str
其中op為編輯操作命令編碼(在此只有插入和刪除操作,1表示插入或2表示刪除操作);pos表示插入或刪除的位置;str表示已經插入或刪除的字符串(中間沒有空格)。各數據間以一個空格分隔。
然后在空一行后,再分行輸入當前將要進行的編輯操作,包括如下四種操作(操作編碼分別為:1表示插入,2表示刪除操作,3表示撤銷(即undo操作),-1表示結束):
1 pos str
表示將在pos位置插入字符串str(中間沒有空格),各數據間以一個空格分隔;
2 pos n
表示將從pos位置開始刪除n個字符(各數據間以一個空格分隔),若要刪除的字符個數多於已有字符個數(即在文本中從pos開始的字符個數小於n),則按實際字符數刪除即可。(提示:為了能夠撤銷刪除操作,應按“2 pos str”形式保存命令。)
3
表示撤銷最近執行的插入或刪除操作,可以進行多次撤銷操作,注意:也可以撤銷之前已經發生過的n步編輯操作中的操作。
-1
表示退出編輯操作,在屏幕上輸出最終編輯后的文本。
要求:
1、上述所有輸入的編輯操作中的字符串str都不包含空白字符(空格符、制表符或換行符);
2、插入操作中的位置pos大於等於0,並且小於等於當前文本的字符個數;0位置表示文本第一個字符的位置;若pos為當前文本的字符個數,則表示在文本最后插入字符串;
3、刪除操作中的位置pos大於等於0,並且小於當前文字的字符個數;
4、若已無操作可撤銷,則再進行撤銷操作無效;
5、文本在編輯過程中,總字符個數不會超過512。
【輸入形式】
先從鍵盤輸入一行字符串,表示已經經過n步編輯操作后的文本串,然后在下一行輸入一個正整數n,並分行輸入n步插入或刪除操作(表示按時間先后順序已進行的操作),格式如上所述。隨后空一行,再分行輸入將要進行的編輯操作,格式如上所述。直到輸入-1操作為止。
【輸出形式】
在屏幕上輸出最終編輯后的文本內容。
題目大意
對單行的文本進行一系列的文本操作,要支持操作的回退。
同時給你的單行文本是在已經給出的幾個操作之后得出的結果。
題目思路
比較直接的思路是用棧來維護每一次操作的序列,需要完成直接的操作和對於操作的撤銷(當然撤銷顯然可以復用正向的操作)。
稍微拓展一下思路甚至可以保存每次操作之后的字符串來做到返回任意一個時刻的狀態?
代碼實現
由於個人對 string 的各種庫函數不算特別熟悉,因此選擇了自己造輪子
#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include<math.h>
#define maxn 605
inline int read() { //快速讀入,可以放在自己的缺省源里面
int x = 0; //數字位
int f = 1; //符號位
char ch = getchar(); //讀入第一個字符
while (!isdigit(ch)) { //不是數字
if (ch == '-') { //特判負號
f = -1;
}
ch = getchar();
}
while (isdigit(ch)) { //讀入連續數字
x = (x << 3) + (x << 1) + ch - '0'; // x * 10 == (x << 3) + (x << 1)
ch = getchar();
}
return x * f;
}
struct change { // change結構體用於記錄每次改變的信息
int type;
int pos;
int n;
char s[maxn];
} c[105];
char s[maxn]; //記錄字符串
int n;
int top; //記錄棧頂
int len; //記錄字符串長度
int max(int a, int b) {
return a > b ? a : b;
}
void del(int pos, int l) {
int i;
for (i = pos + l; i < len; ++i) {
s[i - l] = s[i];
}
len = max(len - l, pos); //注意刪除可能會超過原有的長度
}
void add(int pos, char* c) { //加入新的字符串,題目約束加入的pos < len
int l = strlen(c);
int i;
for (i = len - 1; i >= pos; --i) {
s[i + l] = s[i];
}
len += l;
for (i = 0; i < l; ++i) {
s[pos + i] = c[i];
}
}
int main() {
fgets(s, maxn - 5, stdin);
len = strlen(s);
n = read();
int i;
for (i = 1; i <= n; ++i) { //讀入已經進行過的操作
++top;
scanf("%d", &c[top].type);
if (c[top].type == 1) {
scanf("%d%s", &c[top].pos, c[top].s);
}
if (c[top].type == 2) {
scanf("%d%s", &c[top].pos, c[top].s);
}
if (c[top].type == 3) {
top -= 2;
top = max(top, 0);
}
}
for (i = top; i >= 1; --i) { //撤銷已經進行過的操作
if (c[i].type == 1) {
del(c[i].pos, strlen(c[i].s));
}
if (c[i].type == 2) {
c[i].n = strlen(c[i].s);
add(c[i].pos, c[i].s);
}
}
while (1) { //讀入新的操作
++top;
scanf("%d", &c[top].type);
if (c[top].type == -1) {
break;
}
if (c[top].type == 1) {
scanf("%d%s", &c[top].pos, c[top].s);
}
if (c[top].type == 2) {
scanf("%d%d", &c[top].pos, &c[top].n);
}
if (c[top].type == 3) {
top -= 2;
top = max(top, 0);
}
}
--top;
for (i = 1; i <= top; ++i) { //執行所有有效的操作,得到最后的序列
if (c[i].type == 1) {
add(c[i].pos, c[i].s);
}
if (c[i].type == 2) {
del(c[i].pos, c[i].n);
}
}
s[len] = '\0';
printf("%s", s);
return 0;
}
可能很多同學這里會好奇,為什么要把已經執行過的操作撤銷?
其原因在於,對於已經執行過的操作,我們有着更多的信息(以刪除為例,我們知道他刪除的是哪些字符串),因此我們也更好對這些操作進行撤銷。如果我們要動態支持撤銷后面給出的新操作的話,我們勢必需要保存更多的信息。
而在這個題中,最簡單的撤銷操作其實就是根本不執行。因此,我們撤銷具有完整信息的已執行操作,再用棧維護有效操作的序列,最后按順序執行一次,就能得到最終的結果,同時也能極大地簡化我們的處理。
復雜度分析
刪除和插入操作,涉及到了整個字符串的移動,基本可以認為復雜度是 O(q * |S|) 的 (q為操作數,|S|為字符串最大長度)。
第五題:銀行排隊模擬(生產者-消費者模擬)
題目描述
【問題描述】
一個系統模仿另一個系統行為的技術稱為模擬,如飛行模擬器。模擬可以用來進行方案論證、人員培訓和改進服務。計算機技術常用於模擬系統中。
生產者-消費者(Server-Custom)是常見的應用模式,見於銀行、食堂、打印機、醫院、超等提供服務和使用服務的應用中。這類應用的主要問題是消費者如果等待(排隊)時間過長,會引發用戶抱怨,影響服務質量;如果提供服務者(服務窗口)過多,將提高運管商成本。(經濟學中排隊論)
假設某銀行網點有五個服務窗口,分別為三個對私、一個對公和一個外幣窗口。銀行服務的原則是先來先服務。通常對私業務人很多,其它窗口人則較少,可臨時改為對私服務。假設當對私窗口等待服務的客戶(按實際服務窗口)平均排隊人數超過(大於或等於)7人時,等待客戶將可能有抱怨,影響服務質量,此時銀行可臨時將其它窗口中一個或兩個改為對私服務,當客戶少於7人時,將立即恢復原有業務。設計一個程序用來模擬銀行服務。
說明:
-
增加服務窗口將會增加成本或影響其它業務,因此,以成本增加或影響最小為原則來增加服務窗口,即如果增加一個窗口就能使得按窗口平均等待服務人數小於7人,則只增加一個窗口。一旦按窗口平均等待服務人數小於7人,就減少一個所增加的窗口。
-
為了簡化問題,假設新到客戶是在每個服務周期開始時到達。
-
當等待服務人數發生變化時(新客戶到達或有客戶已接受服務),則及時計算按實際服務窗口平均等待服務人數,並按相應策略調整服務窗口數(增加或減少額外的服務窗口,但對私窗口不能減少)。注意:只在獲取新客戶(不管到達新客戶數是否為0)時或已有客戶去接受服務時,才按策略調整服務窗口數。進一步講,增加服務窗口只在有客戶到達的周期內進行(也就是說增加窗口是基於客戶的感受,銀行對增加窗口是不情願的,因為要增加成本,一旦不再有新客戶來,銀行是不會再增加服務窗口的);一旦有客戶去接受服務(即等待客戶減少),銀行將根據策略及時減少服務窗口,因此,在每個周期內,有客戶去接受服務后要馬上判斷是否減少服務窗口(因為能減少成本,銀行是積極的)
本問題中假設對公和對外幣服務窗口在改為對私服務時及服務期間沒有相應因公或外幣服務新客戶到達(即正好空閑),同時要求以增加成本或影響最小為前提,來盡最大可能減少對私服務客戶等待時間。
【輸入形式】
首先輸入一個整數表示時間周期數,然后再依次輸入每個時間周期中因私業務的客戶數。注:一個時間周期指的是銀行處理一筆業務的平均處理時間,可以是一分鍾、三分鍾或其它。例如:
6
2 5 13 11 15 9
說明:表明在6個時間周期內,第1個周期來了2個(序號分別為1,2),第2個來了5人(序號分別為3,4,5,6,7),以此類推。
【輸出形式】
每個客戶等待服務的時間周期數。輸出形式如下:
用戶序號 : 等待周期數
說明:客戶序號與等待周期數之間用符號:分隔,冒號(:)兩邊各有一個空格,等待周期數后直接為回車。
【樣例輸入】
4
2 5 13 11
【樣例輸出】
1 : 0
2 : 0
3 : 0
4 : 0
5 : 0
6 : 1
7 : 1
8 : 0
9 : 1
10 : 1
11 : 1
12 : 1
13 : 2
14 : 2
15 : 2
16 : 3
17 : 3
18 : 3
19 : 4
20 : 4
21 : 3
22 : 4
23 : 4
24 : 4
25 : 5
26 : 5
27 : 5
28 : 6
29 : 6
30 : 6
31 : 7
【樣例說明】
樣例輸入表明有四個時間周期,第一個周期來了2人(序號1-2);第二個周期來了5人(序號3-7);第三個周期來了13人(序號8-20);第四個周期來了11人(序號21-31)。由於第一個時間周期內只來了2人,銀行(有三個服務窗口)能及時提供服務,因此客戶等待時間為0;第二個時間周期內來了5人,銀行一個周期內一次只能服務3人,另有2個在下個周期內服務,因此等待時間為1,其它類推。
題目大意
根據題目的意思,實現一個動態增加窗口的銀行排隊模擬系統(如果覺得題目有問題的同學,請反復閱讀加粗部分)
題目思路
根據題目的含義,我們需要模擬一個 FCFS (FIRST COME FIRST SERVE)的銀行排隊模型。因此十分顯然,我們需要用到隊列的數據結構來維護服務序列。
代碼實現
#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include<math.h>
#define maxn 1000005
inline int read() { //快速讀入,可以放在自己的缺省源里面
int x = 0; //數字位
int f = 1; //符號位
char ch = getchar(); //讀入第一個字符
while (!isdigit(ch)) { //不是數字
if (ch == '-') { //特判負號
f = -1;
}
ch = getchar();
}
while (isdigit(ch)) { //讀入連續數字
x = (x << 3) + (x << 1) + ch - '0'; // x * 10 == (x << 3) + (x << 1)
ch = getchar();
}
return x * f;
}
struct people {
int time;
int bh;
} q[maxn];
int cnt;
int main() {
int n = read();
int T = 0; // 初始化時間
int head = 1; //初始化隊首
int tail = 0; //初始化隊尾
int C = 3; //初始化窗口數
int i;
for (i = 1; i <= n; ++i) {
++T; //時間增加
int k = read();
while (k--) {
q[++tail] = (struct people){T, ++cnt}; //新加入隊列
}
while (C < 5 && tail - head + 1 >= C * 7) { //根據題設條件增加窗口
++C;
}
int t = C;
while (t-- && head <= tail) { //窗口有剩余並且隊列中有人等待
printf("%d : %d\n", q[head].bh, T - q[head].time); //通過時間差計算出等待時間
++head;
}
while (C > 3 && tail - head + 1 < C * 7) { //根據題設條件減少窗口
--C;
}
}
while (head <= tail) { //隊列中仍然有人等待
++T;
int t = C;
while (t-- && head <= tail) {
printf("%d : %d\n", q[head].bh, T - q[head].time); //通過時間差計算出等待時間
++head;
}
while (C > 3 && tail - head + 1 < C * 7) { //根據題設條件減少窗口
--C;
}
}
return 0;
}
個人認為,利用時間差來算等待時間對這題算是較為不錯的解法。
需要認識到,增加窗口條件的補集,並不是減少窗口的條件。因此,需要嚴格按照題目描述模擬窗口增加和減少。
復雜度分析
每個顧客進隊一次、出隊一次。復雜度顯然是 O(人數) 的。