1. 问题描述:
输入一个只包含加减乖除和括号的合法表达式,求表达式的值。其中除表示整除
输入格式
输入一行,包含一个表达式
输出格式
输出这个表达式的值
样例输入
1-2+3*(4-5)
样例输出
-4
数据规模和约定
表达式长度不超过100,表达式运算合法且运算过程都在int内进行
来源: http://lx.lanqiao.cn/problem.page?gpid=T419
2. 思路分析:
解决这个问题如果使用常规方法来解决是比较困难的而且可能在解决的过程中存在着某些情况考虑不全导致错误,一般来说对于这种具有括号的四则运算表达式可以使用求解出前缀表达式或者后缀表达式来进行求解,将中缀表达式转换成前缀表达式或者后缀表达式之后那么就没有了括号,那么就少了优先级的判定问题了,所以对于计算机来说是比较好处理了
前缀表达式是一种没有括号的算术表达式,与中缀表达式不同的是,其将运算符写在前面,操作式写在后面。为纪念其发明者波兰数学家Jan Lukasiewicz,前缀表达式也称为“波兰式”。例如,- 1+2 3,它等价于1-(2+3)
后缀表达式,又称逆波兰式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行比较常使用的是将后缀表达式来进行计算,下面使用的也是这一种方法
3. 对于1-2+3*(4-5)表达式求解后缀表达式(1 2 - 3 4 5 - * +)的过程如下:
① 其中需要使用一个临时存储+ - * / 符号的栈Stack<Character> stack,存储后缀表达式的队列Queue<String> queue(因为后面要进行后缀表达式的弹出先进的要先弹出来,而且操作数有可能是大于10的那么需要使用把String类型的数字存储到队列),在控制台接收输入的字符串然后转换成字符数组,这样可以对字符数组进行遍历判断是数字还是操作符,遍历过程中如果是操作符那么直接入栈,如果是 " )"那么需要弹出栈中的操作符号,并且把它加入到后缀表达式的队列中,一直到遇到符号栈中的 " ( " 为止,如果不是上面两种那么可能是 + - * / 这些符号或者是左括号那么这个时候需要判断符号栈中的栈顶元素与当前遍历到的字符的优先级的问题
② 涉及到判断优先级的问题,主要遵循下面的规则:
a :不管栈顶元素是左括号还是当前遍历的元素是左括号那么直接压入符号栈
b:当前遍历的元素是 + - 优先级是最低的,因为同级的元素不能够放在一起,所以不管栈顶元素是什么那么都要弹出栈中元素直到当前遍历元素的优先级大于栈顶元素,然后将当前遍历元素压入符号栈即可
c:当前遍历的元素是 * / 这个时候需要判断符号栈中的栈顶元素是什么,如果是* / 那么需要弹出栈顶元素直到当前元素优先级大于栈顶元素,然后将当前遍历元素压入符号栈即可
可以使用一个方法来进行判断
③ 有可能符号栈中的元素还没有完全弹出来,因为输入的表达式中第一个有可能不是括号,所以需要弹出剩下的符号栈中能够的符号,并加入到存储后缀表达式的栈中
经过上面的循环遍历之后那么最终的存储后缀表达式的栈存储的便是后缀表达式了,这个时候需要需要弹出后缀表达式,这个时候使用一个辅助的栈,在弹出符号的过程中假如是判断数字那么直接压入辅助栈中,不是那么需要弹出两个操作数进行计算然后压入辅助栈中,最终在辅助栈中的栈顶元素即是最终计算的结果,输出即可
④ 需要注意的问题:由于声明的的是Queue<String>String类型的队列,在弹出栈中元素判断是否是 + - * / 符号的时候需要使用.equals()方法,不能够使用==来进行判断,否则会在后面将String类型的数字的时候抛出异常(上面的判断出错将符号转换成数字出异常)
3. 代码如下:
Java代码如下:
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
import java.util.Stack;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//注意使用nextLine方法来接收可能的空格
char s[] = sc.nextLine().toCharArray();
/*for(int i = 0; i < s.length; i++){
System.out.println(s[i]);
}*/
long res = solve(s);
System.out.println(res);
sc.close();
}
private static long solve(char[] s) {
Stack<Character> stack = new Stack<Character>();
Queue<String> queue = new LinkedList<String>();
for(int i = 0; i < s.length;){
if(s[i] == ' ') {
i++;
continue;
}
else if(s[i] >= '0' && s[i] <= '9'){
int sum = 0;
//特别要注意i < s.length这个条件
while(i < s.length && s[i] >= '0' && s[i] <= '9'){
sum = sum * 10 + s[i] - '0';
i++;
/*System.out.println("i = " + i);*/
}
queue.add(Integer.toString(sum));
}else if(s[i] == ')'){
while(!stack.isEmpty() && stack.peek() != '('){
queue.add(stack.pop() + "");
}
stack.pop();
i++;
}else{
while(!stack.isEmpty() && compare(stack.peek(), s[i]) < 0){
queue.add(stack.pop() + "");
}
stack.add(s[i]);
i++;
}
}
while(!stack.isEmpty()){
queue.add(stack.pop() + "");
}
//必须要使用.equals方法才正确使用 == 不正确
Stack<Integer> res = new Stack<Integer>();
while(!queue.isEmpty()){
String t = queue.poll();
if(t.equals("+") || t.equals("-") || t.equals("*") || t.equals("/")){
int a = res.pop();
int b = res.pop();
int result = cal(b, a, t);
res.push(result);
}else{
res.add(Integer.parseInt(t));
}
}
return res.pop();
}
private static int cal(int a, int b, String t) {
//计算
if(t.equals("+")){
return a + b;
}else if(t.equals("-")){
return a - b;
}else if(t.equals("*")){
return a * b;
}else{
return a / b;
}
}
private static int compare(char peek, char c) {
if(peek == '(' || c == '(') return 1;
if(c == '+' || c == '-') return -1;
if(c == '*' && (peek == '*' || peek == '/'))return -1;
if(c == '/' && (peek == '*' || peek == '/'))return -1;
return 1;
}
}
C++同样思路的代码如下,下面使用的是数组来充当栈的角色,但是结果是一样的,只是效率上C++代码会更高:
#include <cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
const int maxn=10010;
char str[maxn];
char s[maxn];
int val[maxn];
stack<int> shu;
stack<char> fu;
int solve(int num1,int num2,char ch){
if(ch=='-')return num1-num2;
if(ch=='+')return num1+num2;
if(ch=='*')return num1*num2;
if(ch=='/')return num1/num2;
printf("****\n");
return -1;
}
int cmp(char c1,char c2){
if(c1=='('||c2=='(')return 0;
if(c1=='+'||c1=='-')return 1;
if(c1=='*'&&(c2=='*'||c2=='/'))return 1;
if(c1=='/'&&(c2=='*'||c2=='/'))return 1;
return 0;
}
//1+((23+34)*5)-6
/*
学习了一种逆波兰表达式,就是中序转换成后序
如果是数字的话,直接输出到表达式里面,如果是优先级大于栈顶的话,是直接放入的,包括'('
,如果是')',我们是一直冒到第一个'('为止
*/
int main()
{
memset(s,0,sizeof(s));
scanf("%s",str);
int len=strlen(str);
str[len]=')';
int cur=0;
fu.push('(');//一开始就往栈里面放一个'(',最后在放一个')',这样的话,就不用后面的那个 while()来清栈了
for(int i=0;i<=len;){
if(str[i]>='0'&&str[i]<='9'){
int sum=0;
while(i<len&&str[i]>='0'&&str[i]<='9'){
sum=sum*10+str[i]-'0';
i++;
}
val[cur++]=sum;
}
else{
if(str[i]==')'){
while(!fu.empty()&&fu.top()!='('){
s[cur++]=fu.top();
// printf("**%c\n",fu.top());
fu.pop();
}
fu.pop();
}
else{
while(!fu.empty()&&cmp(str[i],fu.top())){
s[cur++]=fu.top();
fu.pop();
}
fu.push(str[i]);
}
i++;
}
}
for(int i=0;i<cur;i++){
if(!s[i]){
shu.push(val[i]);
printf("val[i] = %d, i = %d s[i] = %c\n", val[i], i, s[i]);
}
else{
//printf("----%d\n", i);
int v2=shu.top();shu.pop();
int v1=shu.top();shu.pop();
int v=solve(v1,v2,s[i]);
shu.push(v);
}
}
printf("%d\n",shu.top());
return 0;
}