数据结构与算法——编程作业——第三章 栈与队列


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