數據結構與算法——編程作業——第三章 棧與隊列


1:中綴表達式的值

總時間限制: 
200ms
 
內存限制: 
1024kB
描述
人們熟悉的四則運算表達式稱為中綴表達式,例如(23+34*45/(5+6+7))。在程序設計語言中,可以利用堆棧的方法把中綴表達式轉換成保值的后綴表達式(又稱逆波蘭表示法),並最終變為計算機可以直接執行的指令,得到表達式的值。

給定一個中綴表達式,編寫程序,利用堆棧的方法,計算表達式的值。
輸入
第一行為測試數據的組數N
接下來的N行,每行是一個中綴表達式。表達式中只含數字、四則運算符和圓括號,操作數都是正整數,數和運算符、括號之間沒有空格。中綴表達式的字符串長度不超過600。
輸出
對每一組測試數據輸出一行,為表達式的值
樣例輸入
3
3+5*8
(3+5)*8
(23+34*45/(5+6+7))
樣例輸出
43
64
108
提示
注意:運算過程均為整數運算(除法運算'/'即按照C++定義的int除以int的結果,測試數據不會出現除數為0的情況),輸出結果也為整數(可能為負)。
中間計算結果可能為負。
這題有點兒變態qaq

解題思路是:中綴表達式-->后綴表達式-->計算后綴表達式

step 1: 中綴表達式-->后綴表達式

中綴表達式a + b*c + (d * e + f) * g,其轉換成后綴表達式則為a b c * + d e * f  + g * +。
轉換過程需要用到棧,具體過程如下:
1)如果遇到操作數,我們就直接將其輸出。
2)如果遇到操作符,則我們將其放入到棧中,遇到左括號時我們也將其放入棧中。
3)如果遇到一個右括號,則將棧元素彈出,將彈出的操作符輸出直到遇到左括號為止。注意,左括號只彈出並不輸出。
4)如果遇到任何其他的操作符,如(“+”, “*”,“(”)等,從棧中彈出元素直到遇到發現更低優先級的元素(或者棧為空)為止。彈出完這些元素后,才將遇到的操作符壓入到棧中。有一點需要注意,只有在遇到" ) "的情況下我們才彈出" ( ",其他情況我們都不會彈出" ( "。
5)如果我們讀到了輸入的末尾,則將棧中所有元素依次彈出。

step 2:計算后綴表達式

后綴表達式的求值過程為,從左到右掃描后綴表達式:
1)如果遇到操作數,將其壓入棧中。
2)如果遇到操作符,則從棧中彈出兩個操作數,計算結果,然后把結果入棧。
3)直到遍歷完后綴表達式,則計算完成,此時的棧頂元素即為計算結果。

我的代碼思路

我在做的時候為了簡化代碼,直接兩大步驟並為一大步驟了,實現思路不變。
具體來說,我用了一個數字棧和一個符號棧來分別儲存操作數和操作符;在遇到step1中(3)(4)(5)的“輸出/彈出操作符”的情況時,實施類似step2中(2)的做法:從數字棧中彈出兩個操作數,從符號棧中彈出一個操作符,計算這三者的運算結果,把結果放回數字棧中;最終,符號棧應該為空,數字棧應該只剩下一個元素,也就是表達式的值,其余情況均為錯誤輸入。

我的代碼

 1 #include <cstdio>
 2 #include <stack>
 3 #include <cstring>
 4 using namespace std;  5 
 6 //判斷運算符號優先級是否滿足c1>=c2
 7 bool prior(const char c1, const char c2){  8     if((c1=='-'||c1=='+') && (c2=='*'||c2=='/'))  9         return false; 10     else
11         return true; 12 } 13 
14 //函數Op2Num(): 操作兩個數字的運算;
15 void Op2Num(stack<int> & num, stack<char> & op){ 16     int num1, num2;//取數字棧頂的兩個操作數
17     num1 = num.top(); num.pop(); 18     num2 = num.top(); num.pop(); 19     switch(op.top()){//操作數字棧頂兩數字並重新入棧
20         case '+': num.push(num2+num1); break; 21         case '-': num.push(num2-num1); break; 22         case '*': num.push(num2*num1); break; 23         case '/': num.push(num2/num1); break; 24         default: printf(" something wrong in Op2Num\n"); 25  } 26     op.pop();//彈出符號棧頂符號
27 } 28 
29 //函數OpeNum(): 30 //cur傳入字符')','+','-','*','/',表明調用本函數的觸發條件
31 void OpeNum(stack<int> & num, stack<char> & op, char cur){ 32     while(!op.empty() && op.top()!='(' && prior(op.top(), cur)) 33  Op2Num(num, op); 34     switch(cur){ 35         case ')': 36             if(op.top() == '(') 37                 op.pop();//彈左括號
38             break; 39         default: 40             op.push(cur);//當前符號入符號棧
41  } 42     return; 43 } 44 
45 //從字符串中取數字
46 int GetNum(char * str, int & i){ 47     int temp = 0; 48     while(str[i]>='0' && str[i]<='9'){ 49         temp = temp*10 + str[i] - '0'; 50         ++i; 51  } 52     --i; 53     return temp; 54 } 55 
56 //表達式結束,所有符號彈棧
57 void PopAll(stack<int> & num, stack<char> & op){ 58     while(!op.empty()) 59  Op2Num(num, op); 60     return; 61 } 62 
63 //中綴表達式運算
64 int CalNifix(char * str){ 65     stack<int> num;//數字棧
66     stack<char> op;//符號棧
67     for(int i=0; str[i]; ++i){ 68         switch(str[i]){ 69             case '(': 70                 op.push('('); break;//左括號入棧
71             case ')': 72             case '+': 73             case '-': 74             case '*': 75             case '/': 76                 OpeNum(num, op, str[i]); break;//操作運算
77             default: 78                 num.push(GetNum(str, i));//取數字入數字棧
79  } 80  } 81     PopAll(num, op);//表達式結束,所有符號彈棧
82     return num.top();//返回運算結果
83 } 84 
85 int main() { 86     int T; 87     scanf("%d", &T); 88     while(T--){ 89         char str[650];//儲存中綴表達式
90         scanf("%s", str); 91         printf("%d\n", CalNifix(str)); 92  } 93     return 0; 94 }
View Code

我的代碼是按照自己的思路寫的,大家當然可以把step1和step2分開做、依次做,我想可能會更容易理解,結構也更清晰。

但是無論如何,請一定一定不要直接copy,一定一定要自己看懂思路后,獨立寫一遍,這樣才能學到東西嘛!加油!

 

2:滑動窗口

總時間限制: 
12000ms
 
內存限制: 
65536kB
描述
給定一個長度為n(n<=10^6)的數組。有一個大小為k的滑動窗口從數組的最左端移動到最右端。你可以看到窗口中的k個數字。窗口每次向右滑動一個數字的距離。
下面是一個例子:
數組是 [1 3 -1 -3 5 3 6 7], k = 3。
窗口位置 最小值 最大值
[1  3  -1] -3  5  3  6  7  -1 3
 1 [3  -1  -3] 5  3  6  7  -3 3
 1  3 [-1  -3  5] 3  6  7  -3 5
 1  3  -1 [-3  5  3] 6  7  -3 5
 1  3  -1  -3 [5  3  6] 7  3 6
 1  3  -1  -3  5 [3  6  7] 3 7
你的任務是得到滑動窗口在每個位置時的最大值和最小值。
輸入
輸入包括兩行。
第一行包括n和k,分別表示數組的長度和窗口的大小。
第二行包括n個數字。
輸出
輸出包括兩行。
第一行包括窗口從左至右移動的每個位置的最小值。
第二行包括窗口從左至右移動的每個位置的最大值。
樣例輸入
8 3
1 3 -1 -3 5 3 6 7
樣例輸出
-1 -3 -3 -3 3 3
3 3 5 5 6 7

曲折歷程

啊,這是一道看似簡單的題目,做了好久才AC。
第一想法是暴力查找,代碼簡單然鵝TLE了,哇地一聲哭出來(不是
為了讓這個暴力代碼更簡潔,我甚至還學了min_element()和max_element()函數;這兩個函數當然是很有用的,可是我用錯了位置,不過還是打算記錄一下
/*這是一段偽代碼,用於指示函數用法*/

#include <algorithm>

std::min_element
    default (1)	
        template <class ForwardIterator>
        ForwardIterator min_element (ForwardIterator first, ForwardIterator last);
    custom (2)	
        template <class ForwardIterator, class Compare>
        ForwardIterator min_element (ForwardIterator first, ForwardIterator last,Compare comp);

std::max_element
    default (1)	
    template <class ForwardIterator>
        ForwardIterator max_element (ForwardIterator first, ForwardIterator last);
    custom (2)
    template <class ForwardIterator, class Compare>
        ForwardIterator max_element (ForwardIterator first, ForwardIterator last,Compare comp);

 程序如下

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;  4 
 5 int main(){  6     int n, k;  7     int t[1000010];  8     scanf("%d %d", &n, &k);  9     for(int i=0; i<n; ++i) 10         scanf("%d", t+i); 11     for(int i=k; i<=n; ++i) 12         printf("%d ",* min_element(t+i-k, t+i)); 13     printf("\n"); 14     for(int i=k; i<=n; ++i) 15         printf("%d ",* max_element(t+i-k, t+i)); 16     printf("\n"); 17     return 0; 18 }
暴力代碼1.0

。。。

 我天真地以為把min_element()和max_element()函數換成for循環就會省時間,畢竟類比一下:用memset()給數組清零比for循環清零要慢許多,於是產生了下面的程序

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;  4 
 5 int main(){  6     int n, k, t_min, t_max;  7     int t[1000010], m[1000010];  8     scanf("%d %d", &n, &k);  9     for(int i=0; i<n; ++i) 10         scanf("%d", t+i); 11     for(int i=k; i<=n; ++i){ 12         t_min = t_max = t[i-k]; 13         for(int j=i-k+1; j<i; ++j){ 14             t_min = min(t_min, t[j]); 15             t_max = max(t_max, t[j]); 16  } 17         m[i] = t_max; 18         printf("%d ", t_min); 19  } 20     printf("\n"); 21     for(int i=k; i<=n; ++i) 22         printf("%d ", m[i]); 23     printf("\n"); 24     return 0; 25 }
暴力代碼1.1

不出預料地再次TLE

正經答案

最終我向度娘屈服了,查了一下,原來大家都是用單調隊列實現解決滑動窗口問題的,貼幾個講得比較詳細的帖子鏈接,因為我懶hh

1.https://www.acwing.com/solution/acwing/content/2499/

2.https://www.cnblogs.com/llke/p/10780121.html

我自己寫的代碼如下:

 1 #include <iostream>
 2 using namespace std;  3 
 4 #define N 1000010
 5 
 6 //輸出滑動窗口最小值
 7 void PrintMin(int * num, const int n, const int k){  8     //p_min[head...tail]儲存當前窗口內的單調序列
 9     int p_min[n], head=0, tail=-1; 10     for(int i=0; i<n; ++i){ 11         while(head<=tail && num[i]<=num[p_min[tail]]) 12             --tail;//隊尾大元素元素出隊
13         p_min[++tail] = i;//元素i入隊尾
14         while(p_min[head]<=i-k) 15             ++head;//隊頭窗口外元素出隊
16         if(i>=k-1)//輸出當前窗口最小值
17             cout << num[p_min[head]] << ' '; 18  } 19     cout << endl; 20 } 21 
22 //輸出滑動窗口最大值
23 void PrintMax(int * num, const int n, const int k){ 24     int p_max[n], head=0, tail=-1; 25     for(int i=0; i<n; ++i){ 26         while(head<=tail && num[i]>=num[p_max[tail]]) 27             --tail; 28         p_max[++tail] = i; 29         while(p_max[head]<=i-k) 30             ++head; 31         if(i>=k-1) 32             cout << num[p_max[head]] << ' '; 33  } 34     cout << endl; 35 } 36 
37 int main() { 38     int n, k, num[N]; 39     cin >> n >> k; 40     for(int i=0; i<n; ++i) 41         cin >> num[i]; 42  PrintMin(num, n, k); 43  PrintMax(num, n, k); 44     return 0; 45 }
滑動窗口1.0

觀察發現,PrintMin()和PrintMax()函數的重復之處過多,唯一的差別在於“--tail”的時候是“隊尾大元素出隊”還是“隊尾小元素出隊”。很自然地想到,我們可以利用函數指針傳遞一個比較函數,以簡化程序、合二為一。代碼如下:

 1 #include <iostream>
 2 using namespace std;  3 
 4 #define N 1000010
 5 
 6 bool MyMin(int a, int b){return a<=b;}  7 bool MyMax(int a, int b){return a>=b;}  8 
 9 void Print(int * num, const int n, const int k, bool (*f)(int,int)){ 10     //p[head...tail]儲存當前窗口內的單調序列
11     int p[n], head=0, tail=-1; 12     for(int i=0; i<n; ++i){ 13         while(head<=tail && f(num[i],num[p[tail]])) 14             --tail;//隊尾元素出隊
15         p[++tail] = i;//元素i入隊尾
16         while(p[head]<=i-k) 17             ++head;//隊頭窗口外元素出隊
18         if(i>=k-1)//輸出當前窗口最值
19             cout << num[p[head]] << ' '; 20  } 21     cout << endl; 22 } 23 
24 int main() { 25     int n, k, num[N]; 26     cin >> n >> k; 27     for(int i=0; i<n; ++i) 28         cin >> num[i]; 29  Print(num, n, k, MyMin); 30  Print(num, n, k, MyMax); 31     return 0; 32 }
滑動窗口2.0

后來又想到,可以通過把cin-cout換成scanf-printf,以及在函數前加上inline字樣來減少運行用時,代碼如下:

 1 #include <stdio.h>
 2 using namespace std;
 3 
 4 #define N 1000010
 5 
 6 bool MyMin(int a, int b){return a<=b;}
 7 bool MyMax(int a, int b){return a>=b;}
 8 
 9 inline void Print(int * num, const int n, const int k, bool (*f)(int,int)){
10     //p[head...tail]儲存當前窗口內的單調序列
11     int p[n], head=0, tail=-1;
12     for(int i=0; i<n; ++i){
13         while(head<=tail && f(num[i],num[p[tail]]))
14             --tail;//隊尾元素出隊
15         p[++tail] = i;//元素i入隊尾
16         while(p[head]<=i-k)
17             ++head;//隊頭窗口外元素出隊
18         if(i>=k-1)//輸出當前窗口最值
19             printf("%d ", num[p[head]]);
20     }
21     printf("\n");
22 }
23 
24 int main() {
25     int n, k, num[N];
26     scanf("%d %d", &n, &k);
27     for(int i=0; i<n; ++i)
28         scanf("%d", num+i);
29     Print(num, n, k, MyMin);
30     Print(num, n, k, MyMax);
31     return 0;
32 }
滑動窗口3.0

滑動窗口1.0、2.0、3.0(從上到下)的OJ運行測試結果如下:

 可以看到它們在運行效率上有了一些提升,而且我還學到了不少新東西,雖然花了很多時間,但還是好開星!!!

 

3:棧的基本操作

總時間限制: 
1000ms
 
內存限制: 
1000kB
描述

棧是一種重要的數據結構,它具有push k和pop操作。push k是將數字k加入到棧中,pop則是從棧中取一個數出來。

棧是后進先出的:把棧也看成橫向的一個通道,則push k是將k放到棧的最右邊,而pop也是從棧的最右邊取出一個數。

假設棧當前從左至右含有1和2兩個數,則執行push 5和pop操作示例圖如下:

          push 5          pop

棧   1 2  ------->  1 2 5 ------>  1 2

現在,假設棧是空的。給定一系列push k和pop操作之后,輸出棧中存儲的數字。若棧已經空了,仍然接收到pop操作,

則輸出error。

輸入
第一行為m,表示有m組測試輸入,m<100。
每組第一行為n,表示下列有n行push k或pop操作。(n<150)
接下來n行,每行是push k或者pop,其中k是一個整數。
(輸入保證同時在棧中的數不會超過100個)
輸出
對每組測試數據輸出一行。該行內容在正常情況下,是棧中從左到右存儲的數字,數字直接以一個空格分隔,如果棧空,則不作輸出;但若操作過程中出現棧已空仍然收到pop,則輸出error。
樣例輸入
2
4
push 1
push 3
pop
push 5
1
pop
樣例輸出
1 5
error

我的答案

 1 #include <stdio.h>
 2 #include <stack>
 3 using namespace std;  4 
 5 //函數:操作棧
 6 void OpStack(){  7     stack<int> st;  8     int n, t;  9     char cmd[5]; 10     bool flag=1; 11     scanf("%d", &n); 12     while(n--){ 13         scanf("%s", cmd); 14         if(cmd[1] == 'u'){//push
15             scanf("%d", &t); 16  st.push(t); 17  } 18         else{//pop
19             if(!st.empty()) 20  st.pop(); 21             else//error
22                 flag = 0; 23  } 24  } 25     if(flag){//從棧底向棧頂輸出,要倒一下
26         stack<int> _st; 27         while(!st.empty()){ 28  _st.push(st.top()); 29  st.pop(); 30  } 31         while(!_st.empty()){ 32             printf("%d ", _st.top()); 33  _st.pop(); 34  } 35  } 36     else
37         printf("error"); 38     printf("\n"); 39     return; 40 } 41 
42 int main() { 43     int m; 44     scanf("%d",&m); 45     while(m--) 46  OpStack(); 47     return 0; 48 }
View Code

這題比較簡單~

 

我去寫lab了,一起加油w

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM