最近准备企业实习和夏令营/预推免,计划从现在开始到四月底,把数据结构和算法全部复习一遍,主要是参考了《算法笔记》(胡凡、曾磊等著)这本书,从第七章提高篇开始,大约一周推进一章。
由于不是初学者了,在编程过程中会经常使用stack、queue等接口,以及vector、set、map等容器,从而更加方便快捷地实现。
在复习的过程中,我会从CSP认证考试,本书(很多例题来自于浙大PAT以及codeup),LeetCode(力扣),以及蓝桥杯等平台中选取一些典型的题目,作为训练,并且加以总结和整理。
《算法笔记》第7章 提高篇(1)——数据结构专题(1)
7.1 栈的应用
例题【codeup 1918】
题目描述:读入一个只包含+,-,*,/的非负整数计算表达式,计算表达式的值
输入格式:5 + 2 * 3 / 49 - 4 / 13
输出:4.81
解决思路
第一步 中缀转后缀(对应Change()函数)
1)创建一个操作符栈(stack<node>s),临时存放操作符;
创建一个队列(或数组)(queue<node>q),存放后缀表达式
2)从左向右扫描中缀表达式
若读到操作数(注意这边因为每次只读一个字符,所需当连续读到多个数字字符时要做一个转换,比如'2''2''3'转化为223,用一个while{ }循环即可);
若读到操作符,需要比较优先级
- 当读到的操作符的优先级高于栈顶操作符优先级,压入栈顶
- 当读到的操作符的优先级低于或等于栈顶操作符优先级,则将栈中操作符不断弹出,到后缀表达式,直至读到的操作符的优先级高于栈顶操作符优先级,再压入栈顶
3)扫描完成后,将栈中剩余元素依次弹至后缀表达式队列(或数组)中
第二步 计算后缀表达式(对应Cal()函数)
从左到右扫描后缀表达式
若为操作数,压入栈
若为操作符,从栈中连续弹出两个操作数,先弹出的是第二个操作数(如除法中的被除数),后弹出的是第一个操作数(如除非中的除数),计算以后将生成的结果压入栈
如此反复,直至后缀表达式扫描完毕,这时候栈中只有一个数,也即是最终运算结果。
代码及注释如下
#include<iostream>
#include<cstdio>
#include<string>
#include<stack>
#include<queue>
#include<map>
using namespace std;
struct node{
double num;//操作数
char op;//操作符
bool flag;//true表示操作数 false表示操作符
};
string str;
stack<node>s;//操作符栈
queue<node>q;//后缀表达式序列(也可以用数组 这里为了练习队列使用queue)
map<char,int>op;//用map建立操作符和优先级映射(不用map直接if判断也行 这里为了练习map)
void Change(){//函数Change()将中缀表达式转化为后缀表达式
double num;
node temp;
for(int i=0;i<str.length();){
if(str[i]>='0'&&str[i]<='9'){//若为操作数
temp.flag=true;
temp.num=str[i++]-'0';
while(i<str.length()&&str[i]>='0'&&str[i]<='9'){
temp.num *= 10;
temp.num += (str[i]-'0');
i++;
}//因为是字符串形式,且每次只读一个字符,因此连续读入多个数字字符时需要转化为数字
q.push(temp);
}else{//若为操作符
temp.flag=false;
while(!s.empty()&&op[str[i]]<=op[s.top().op]){
//若op的优先级低于或等于栈顶操作符的优先级,则将操作符栈的操作符不断弹出到后缀表达式中 e.g. 3+2*5 该表达式中2*5需要先计算,因此/*/先入队列(位置越前越先计算);
//注意:是低于或等于,e.g. 2/3*4 中2/3要先计算 所以'/'需要先进后缀表达式队列
q.push(s.top());
s.pop();
}
temp.op = str[i];// 若op的优先级高于栈顶操作符的优先级,则压入操作符栈 e.g.
s.push(temp);
i++;
}
}
while(!s.empty()){//上述操作结束后,若操作栈中还有元素,则依次弹出至后缀表达式中
q.push(s.top());
s.pop();
}
}
double Cal(){//计算后缀表达式
double temp1,temp2;//操作数1 操作数2
node cur,temp;
while(!q.empty()){
cur=q.front();//
q.pop();
if(cur.flag==true) s.push(cur);//如果是操作数,则压入栈
else{
temp2 = s.top().num;//这里要注意先pop的是运算时的第二个操作数(如除非中的除数)
s.pop();
temp1 = s.top().num;//后pop的是第一个操作数(如除非中的被除数)
s.pop();
temp.flag = true;
if(cur.op=='+')temp.num = temp1+temp2;
else if(cur.op=='-')temp.num=temp1-temp2;
else if(cur.op=='*')temp.num=temp1*temp2;
else if(cur.op=='/')temp.num=temp1/temp2;
s.push(temp);
}
}
return s.top().num;
}
int main(){
op['+']=op['-']=1;//利用map设定操作符优先级
op['*']=op['/']=2;
while(getline(cin,str),str!="0"){
for(string::iterator it=str.end();it !=str.begin();it--){
if(*it==' ')str.erase(it);//string的erase()可以消除空格
}
while(!s.empty())s.pop();//初始化栈
Change();
printf("%.2f\n",Cal());
}
}
示例及运行结果
扩展
若表达式中出现( ),处理也很简单,只需要在中缀转后缀时加一个判断,即读到')'则压入栈,读到')'则将栈中元素不断弹出直至遇到'(',因为括号决定了部分表达式的优先级,括号里的要优先计算,所以遇到')'就弹出括号内的内容到后缀表达式中即可。
相关题目
CSP 201903-2 二十四点
括号匹配()【】{} 今天下午微软面试的也问到了
Codeup Contest ID:100000605