zoukankan      html  css  js  c++  java
  • 数据结构与算法——编程作业——第三章 栈与队列

    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
    ");
    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
    ", 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("
    ");
    14     for(int i=k; i<=n; ++i)
    15         printf("%d ",* max_element(t+i-k, t+i));
    16     printf("
    ");
    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("
    ");
    21     for(int i=k; i<=n; ++i)
    22         printf("%d ", m[i]);
    23     printf("
    ");
    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("
    ");
    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("
    ");
    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

  • 相关阅读:
    c# 之 事务
    Asp.Net 之 js/jquery获取服务器端控件
    产品能力框架图
    软件测试职业发展规划图
    Loadrunner 工作原理图
    Linux内核性能测试工具全景图
    Linux 性能监控分析
    软件藏宝图
    Haproxy 8种算法+Session共享
    Keepalived+Haproxy实现高可用负载均衡
  • 原文地址:https://www.cnblogs.com/tanshiyin-20001111/p/11590126.html
Copyright © 2011-2022 走看看