表达式求值
一.概述
中缀表达式求值可以先将中缀转后缀,再用后缀计算结果,但是,有点太麻烦,而另一种方式是利用两个栈直接求值,中缀表达式求值基本过称为:
定义两个栈,s1存储数字,s2存储运算符
1. 从左到右扫描,如果扫描到的运算符优先级大于栈顶运算符优先级,则入栈s2,否则,出栈并运算:从s1出两个数,s2出一个运算符,将计算后的结果送入s1
2. 如果遇到右括号,一直出栈,直到遇到左括号为止。并且每一次出栈的运算符都要做一次运算。如果遇到左括号则直接压入栈,括号的优先级最高
3. 扫描完整个表达式后,S2栈中如果还有运算符剩余,则全部出栈,逐个计算即可。
二.需要注意的问题
1.如何处理运算符号优先级问题
‘+’/’-’ < ‘*’, ‘/’ , ‘%’ < ‘^’
可以用不同的数字代表不同等级的运算符的优先级
public static int compare(char x) //确定运算符类型的优先级,同个数值同种优先级,数字大小表示优先级大小,用于下一步的判断
{
switch(x)
{
case '+': return 0;
case '-': return 0;
case '*': return 1;
case '/': return 1;
case '%': return 1;
case '^': return 2;
}
return -1;
}
2.如何比较栈顶的运算符和输入的运算符的优先级大小
public static boolean compare(char a,char b) //比较栈中字符的优先级,判断是否直接压栈还是出栈
{
if(b=='(')
{
return false; //如果栈顶为左括号,无论什么情况都压入栈
}
if(compare(a)<=compare(b))
{
return true; //可以将栈中的运算符弹出
}
else return false; //压入栈中
}
3.如何根据运算符对运算栈的数进行运算
public static void calculate(char x,Stack<Double> st2)//根据x运算符对运算栈的数进行运算
{
if(st2.size()<2)
{
System.out.print("1表达式不合法/");
System.exit(-1);
}
else
{
double a=st2.pop();
double b=st2.pop();
switch(x)
{
case '+': st2.push(a+b);
break;
case '-': st2.push(b-a);
break;
case '*': st2.push(a*b);
break;
case '/':
if(a==0)
{
System.out.print("表达式不合法,因为除数不能为0");
System.exit(-1);
}
st2.push(b/a);
break;
case '%':
if(is_(a)||is_(b))
{
System.out.print("%运算不能有小数参与!");
System.exit(-1);
}
st2.push(b%a);
break;
case '^':
if(is_(a))
{
System.out.println("指数不能为小数");
System.exit(-1);
}
st2.push((double)Math.pow(b,a));
}
}
}
4.怎么确定数的类型以及数的位数,以及怎么分辨出数和符号
设置两个boolean型的变量:tag(显示是否为小数),get(判断是否有数要读入栈),一个double型的变量sum用于记录当前取到的数值,一个int型的变量r,用于显示小数位.
另外,符号用compare()确定;
boolean tag=false; //显示是否为小数
boolean get=false; //判断是否有数要读入栈
double sum=0;
int r=1; //显示小数点后有几位数
for(int i=0;i<s.length();i++)
{
char x=s.charAt(i);
if(x>='0'&&x<='9') //数字
{
sum=sum*10+x-'0';
get=true;
}
else if(x=='.')
{
tag=true;
continue;
}
else if(x=='(')
{
}
else if(x==')')
{
.
.
.
r=1;
tag=false;
get=false;
sum=0; //对栈里的数字处理完后各个状态的更新
.
.
.
}
else if(compare(x)!=-1) //符号
{
if(get==true)
{
if(tag==true)
{
sum=(double)sum/r; //小数的话需要移动小数点
}
st2.push(sum);
}
r=1;
tag=false;//读取一个数,如果是小数则取消小数的标志位
get=false;
sum=0; //更新数
.
.
.
}
else //其他不合法的符号
{
}
if(tag==true) //计算小数点后几位数
{
r*=10;
}
}
6.对输入的表达式进行逐一处理,比如扫描到左括号,右括号,数字,符号,非法符号怎么处理.
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
int flag=0; //检测左括号的数量
boolean tag=false; //显示是否为小数
boolean get=false; //判断是否有数要读入栈
double sum=0;
int r=1; //显示小数点后有几位数
String s=sc.nextLine();
Stack<Character> st1=new Stack<>(); //符号栈
Stack<Double> st2=new Stack<>(); //运算栈
for(int i=0;i<s.length();i++)
{
char x=s.charAt(i);
if(x>='0'&&x<='9') //数字
{
sum=sum*10+x-'0';
get=true;
}
else if(x=='.')
{
tag=true;
continue;
}
else if(x=='(')
{
st1.push(x); //左括号直接入栈
flag++; //数量加一
}
else if(x==')')
{
if(flag==0) //如果没有左括号就不合法
{
System.out.print("2表达式不合法");
System.exit(-1);
}
//符号栈里肯定有左括号
if(get==true) //获取到一个数字
{
if(tag==true) //这个数是小数
{
sum=(double)sum/r;
}
st2.push(sum);
}
r=1;
tag=false;
get=false;
sum=0;
while(!st1.empty()&&st1.peek()!='(') //将左括号和右括号之间的运算数计算完
{
calculate(st1.pop(),st2);
}
st1.pop(); //弹出左括号
flag--; //左括号数剪一
}
else if(compare(x)!=-1) //符号
{
if(get==true)
{
if(tag==true)
{
sum=(double)sum/r; //小数的话需要移动小数点
}
st2.push(sum);
}
r=1;
tag=false;//读取一个数,如果是小数则取消小数的标志位
get=false;
sum=0; //更新数
if(st1.empty()) //符号栈为空,直接压进栈
{
st1.push(x);
}
else //如果新加进的符号优先级比栈顶符号优先级低或相等,可以先进行计算
{
while(!st1.empty()&&compare(x,st1.peek()))
{
calculate(st1.pop(),st2);
}
st1.push(x);
}
}
else //其他不合法的符号
{
System.out.print("3表达式不合法");
System.exit(-1);
}
if(tag==true) //计算小数点后几位数
{
r*=10;
}
}
if(get==true)
{
if(tag==true)
{
sum=(double)sum/r; //小数的话需要移动小数点
}
st2.push(sum);
}
r=1;
tag=false;//读取一个数,如果是小数则取消小数的标志位
get=false;
sum=0; //更新数
while(!st1.empty()&&!st2.empty())
{
calculate(st1.pop(),st2);
}
if(st1.empty()&&st2.size()==1) System.out.println("运算结果是"+String.format("%.4f", st2.pop()));
else System.out.println("4表达式不合法");
}
5.如何判断该表达式是否合法
表达式如果不合法,主要体现在如下几个方面:
1.检验左右括号不匹配问题
设置变量flag记录左括号的个数,每当读取一个右括号的时候,左括号的个数都会减一,到最终检验左右括号是否存在多余.
右括号比左括号多的情况:
int flag=0; //检测左括号的数量
.
.
.
. else if(x=='(')
{
st1.push(x); //左括号直接入栈
flag++; //数量加一
}
else if(x==')')
{
if(flag==0) //如果没有左括号就不合法
{
System.out.print("2表达式不合法");
System.exit(-1);
}
//符号栈里肯定有左括号
........
flag--; //左括号数减一
左括号比右括号多的情况:
public static void calculate(char x,Stack<Double> st2)//根据x运算符对运算栈的数进行运算
{
if(st2.size()<2)
{
System.out.print("1表达式不合法/"); //会在这里报错
System.exit(-1);
}
..
.
.
.
.
.}
2.出现其他非运算符
for(int i=0;i<s.length();i++)
{
char x=s.charAt(i);
if(x>='0'&&x<='9') //数字
{
}
else if(x=='.')
{
}
else if(x=='(')
{
}
else if(x==')')
{
}
else if(compare(x)!=-1) //符号
{
}
else //其他不合法的符号
{
System.out.print("3表达式不合法");
System.exit(-1);
}
int compare(char x)函数会返回-1
3.运算中时的除数为0,指数为小数或者进行取余运算时出现小数
判断该数是否为小数:
public static boolean is_(double x) //判断该数是否有小数位
{
if(x%1==0) //为整数
{
return false;
}
else return true;
}
在s2中取出运算数时进行检验
public static void calculate(char x,Stack<Double> st2)//根据x运算符对运算栈的数进行运算
{
if(st2.size()<2)
{
}
else
{
double a=st2.pop();
double b=st2.pop();
switch(x)
{
case '/':
if(a==0)
{
System.out.print("表达式不合法,因为除数不能为0");
System.exit(-1);
}
st2.push(b/a);
break;
case '%':
if(is_(a)||is_(b))
{
System.out.print("%运算不能有小数参与!");
System.exit(-1);
}
st2.push(b%a);
break;
case '^':
if(is_(a))
{
System.out.println("指数不能为小数");
System.exit(-1);
}
st2.push((double)Math.pow(b,a));
}
}
}
4.其他匹配问题
会在这个函数中检验出
一个运算符参与运算需要两个运算数,如果找不到两个运算数肯定是不合法的表达式
public static void calculate(char x,Stack<Double> st2)//根据x运算符对运算栈的数进行运算
{
if(st2.size()<2)
{
System.out.print("1表达式不合法/");
System.exit(-1);
}
else
{
}
}
体会
这道题的细节还是挺多的,像如何处理多位数,如何判断是否小数,如何检验表达式的合法性等,我觉得在编写这样的程序最好从顶向下设计,确定整个程序的大概框架,再不断填充细节,整个程序化成一个个小问题去解决,一个个小函数去检验正确性,这样查找问题比较容易. (函数的封装性挺有趣的)
不足
这个程序只能处理除了负数之外的各种加减乘除取余和幂运算,并能检验其正确性.如果出现了负数,则会发生错误.
扩展
根据中缀表达式求值也可通过转换成后缀表达式再进行求值
(只实现10以内的加减乘除)
public class Sta {
public static int calculate(int x,int y,char c)
{
switch(c)
{
case '+': return x+y;
case '-': return y-x;
case '*': return x*y;
case '/': if(x==0)
{
System.out.println("除数不能为0,表达式不合理");
System.exit(-1);
}
return y/x;
}
return -1;
}
public static int transform(String str)
{
Stack<Integer> s=new Stack<>();
for(int i=0;i<str.length();i++)
{
char c=str.charAt(i);
if(Isdigit(c))
{
s.push(c-'0');
}
else
{
if(s.size()<2)
{
System.out.println("3表达式不合理");
System.exit(-1);
}
int a=s.pop();
int b=s.pop();
int sum=calculate(a,b,c);
s.push(sum);
}
}
if(s.size()==1) return s.pop();
return -1;
}
public static int compare(char c) //比较符号的优先级
{
switch(c)
{
case '+': return 0;
case '-': return 0;
case '*': return 1;
case '/': return 1;
}
return -1;
}
public static boolean Isdigit(char x) //判读是不是数字型
{
if(x>='0'&&x<='9') return true;
else return false;
}
public static boolean com(char a,char b) //判断符号a是否压入栈
{
if(b=='(') //如果栈顶是左括号,无论什么情况a都压入栈
{
return true;
}
if(compare(a)>compare(b))
{
return true;
}
return false;
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
Stack<Character>s=new Stack<>();//运算符栈
String str=sc.nextLine();
StringBuffer sb=new StringBuffer(); //可变字符串
int count=0;//左括号数量
for(int i=0;i<str.length();i++)
{
char c=str.charAt(i);
if(Isdigit(c)) sb=sb.append(c);//是数字的话就直接添加
else //非数字
{
if(c=='(')
{
count++;
s.push(c); //左括号直接压栈
}
else if(c==')')
{
if(count==0) //没有左括号,但却有右括号,肯定不合法
{
System.out.println("1表达式不合法");
System.exit(-1);
}
while(!s.isEmpty()&&s.peek()!='(')//将左右括号之间的符号收进字符串中
{
sb=sb.append(s.pop());
}
s.pop();//将左括号弹出
count--;
}
//合法表达式
else if(compare(c)!=-1)//将栈顶符号和当前符号作比较
{
if(s.isEmpty()) s.push(c);
else
{
char x=s.peek();
if(com(c,x)) s.push(c);
else
{
while(!s.isEmpty()&&!com(c,x))
{
x=s.pop();
sb=sb.append(x);
}
s.push(c);
}
}
}
else
{
System.out.println("2表达式不合法");
System.exit(-1);
}
}
}
while(!s.isEmpty())
{
sb=sb.append(s.pop());
}
System.out.println(transform(sb.toString()));
}
}