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

摘要:
在程序设计语言中,可以利用堆栈的方法把中缀表达式转换成保值的后缀表达式,并最终变为计算机可以直接执行的指令,得到表达式的值。给定一个中缀表达式,编写程序,利用堆栈的方法,计算表达式的值。输入第一行为测试数据的组数N接下来的N行,每行是一个中缀表达式。中缀表达式的字符串长度不超过600。中间计算结果可能为负。3)直到遍历完后缀表达式,则计算完成,此时的栈顶元素即为计算结果。

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 namespacestd;
5 
6 //判断运算符号优先级是否满足c1>=c2
7 bool prior(const char c1, const charc2){
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, charcur){
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     returntemp;
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 intmain() {
86     intT;
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。
窗口位置最小值最大值
[13-1]-35367-13
1[3-1-3]5367-33
13[-1-35]367-35
13-1[-353]67-35
13-1-3[536]736
13-1-35[367]37
你的任务是得到滑动窗口在每个位置时的最大值和最小值。
输入
输入包括两行。
第一行包括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 namespacestd;
4 
5 intmain(){
6     intn, 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

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

我天真地以为把min_element()和max_element()函数换成for循环就会省时间,毕竟类比一下:用memset()给数组清零比for循环清零要慢许多,于是产生了下面的程序

1 #include <cstdio>
2 #include <algorithm>
3 using namespacestd;
4 
5 intmain(){
6     intn, 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

数据结构与算法——编程作业——第三章 栈与队列第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 namespacestd;
3 
4 #define N 1000010
5 
6 //输出滑动窗口最小值
7 void PrintMin(int * num, const int n, const intk){
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 intk){
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 intmain() {
38     intn, 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 namespacestd;
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 intmain() {
25     intn, 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 namespacestd;
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 intmain() {
25     intn, 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张

可以看到它们在运行效率上有了一些提升,而且我还学到了不少新东西,虽然花了很多时间,但还是好开星!!!

3:栈的基本操作

总时间限制:
1000ms
内存限制:
1000kB
描述

栈是一种重要的数据结构,它具有push k和pop操作。push k是将数字k加入到栈中,pop则是从栈中取一个数出来。

栈是后进先出的:把栈也看成横向的一个通道,则push k是将k放到栈的最右边,而pop也是从栈的最右边取出一个数。

假设栈当前从左至右含有1和2两个数,则执行push 5和pop操作示例图如下:

push 5pop

栈 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 namespacestd;
4 
5 //函数:操作栈
6 voidOpStack(){
7     stack<int>st;
8     intn, 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 intmain() {
43     intm;
44     scanf("%d",&m);
45     while(m--)
46 OpStack();
47     return 0;
48 }
View Code

这题比较简单~

我去写lab了,一起加油w

免责声明:文章转载自《数据结构与算法——编程作业——第三章 栈与队列》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇将 notepad++ 添加到鼠标右键菜单 带图标Ansible之路——第九章:Ansible Playbook下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

redis学习(一)

一.redis简介 Redis是基于内存、可持久化的日志型、key-value高性能存储系统。关键字(Keys)是用来标识数据块。值(Values)是关联于关键字的实际值,可以是任何东西。有时候你会存储字符串,有时候是整数,还有时候你会存储序列化对象(使用JSON、XML等)。在大多数情况下,Redis会把值看做是一个字节序列,而不会关注它们实质上是什么。...

Python解析Pcap包类源码学习

0x1、前言 ​ 在现场取证遇到分析流量包的情况会比较少,虽然流量类设备原理是把数据都抓出来进行解析,很大一定程度上已经把人可以做的事情交给了机器自动完成。 ​ 可用于PCAP包分析的软件比如科来,Wireshark都是很好用的分析软件,找Pcap解析的编程类代码时发现已经有很多大佬写过Python脚本辅助解析Pcap,也有提取将Pcap信息以界面形式展示...

Qt中文乱码问题(比较清楚,同一个二进制串被解释成不同的语言)

文章来源:http://blog.csdn.net/brave_heart_lxl/article/details/7186631 以下是dbzhang关于qt中文乱码问题原因的阐述,觉得不错: 首先呢,声明一下,QString 是不存在中文支持问题的,很多人遇到问题,并不是本身 QString 的问题,而是没有将自己希望的字符串正确赋给QString。...

PHP 字符串左边补0,字符串右边补0

概述:项目中经常会使用到在一串编码左边、右边甚至中间自动填充制定字符如“0” 并且制定填充后的字符串长度。 函数str_pad:该函数返回 input 被从左端、右端或者同时两端被填充到制定长度后的结果。 这样说可能不太明白,我们来看个案例: str_pda('被补充的字符串’,'补充完后字符串的长度','用什么字符补充','STR_PAD...'); S...

Java如何从HttpServletRequest中读取HTTP请求的body

首先贴出原文地址,尊重原作者 http://blog.csdn.net/zxygww/article/details/47045055 注意:下面方法已验证通过。 HTTP请求中的是字符串数据: //字符串读取 void charReader(HttpServletRequest request) { BufferedReader br = reque...

【数据结构】二叉树、普通树与森林的定义与应用

<body> 普通\(m\)叉树的性质(普通二叉树也满足) 各层的最大结点个数 \[第i层最多有m^{i-1}个结点,其中1\le i\le h \] 高度为h的\(m\)叉树最多结点个数 \[等比数列求和公式:\frac{m^h-1}{m-1} \] 具有\(n\)个结点的\(m\)叉树至少有多高 也就是说完全m叉树的...