前几天博主去参加一家企业的笔试,其中编程题的最后一道就是数学表达式求值的问题,由于数据结构已经学过好长时间了,所以就忘了,回来之后就赶紧翻书查看,才知道在栈和二叉树这两章中都有关于数学表达式求值的问题(由此可见,数据结构和算法这些基础知识对程序员来说有多重要,所以一定要好好学!)。接下来楼主就将自己了解到的知识分享出来,供大家参考,不足之处,还请各位多多指教!
算术表达式转成后缀表达式(逆波兰式)
我们常见的表达式都是类似于(a+b)*c-d/e*f之类的形式,这种形式都于人来说很容易理解,因为我们很清楚这些运算符的优先级,所以会选择哪些先算哪些后算,但是对于计算机来说,这种方式却很难办到。因此,我们需要将计算的方式设计的越简单越好,越简单的计算形式越便于计算机处理,执行效率也就更高,准确率也更高。因此,计算机方面的大牛们就设计了前缀表达式(波兰表达式)、中缀表达式、后缀表达式(逆波兰式),这三种表达式是根据遍历二叉树的不同顺序得出来的,相信学过数据结构的同学对这几种方式并不陌生。好了,废话不多说,接下来,我们就看一下如何将一个数学表达式转成后缀表达式并利用后缀表达式来计算结果。
如何将(a+b)*c-d/e*f转成后缀表达式呢?
我们约定 ‘*’,‘/’两种运算符的优先级最高,用整数3表示,‘+’,‘-’的运算符的优先级次之,用2表示,‘(’的优 先级最低,用1表示;
具体思想如下:
1. 建立一个栈用来保存操作符,初始时为空;
2. 从左至右遍历输入的算术表达式;
1) 如果当前字符为操作数,则直接加入到后缀表达式中;
2) 如果当前字符为操作符,当栈为空直接将此操作符加入到栈中,否则就将栈中所有操作符优先级小于当前操作符优先级的操作 符全部加入 到后缀表达式中(如果栈不为空且栈顶操作符的优先级大于等于当前操作符的优先级就将栈顶元素出栈并加入后缀表达式中,直到栈为空或栈顶 操作符的优先级小于当前操作符的优先级),最后将当前操作符入栈。
3) 如果当前字符为‘(',直接将其加入操作符栈中;
4) 如果当前字符为‘)’,就将栈顶操作符出栈,直到遇到第一个‘(’为止。(注意,需要将‘(’从操作数 栈中取出但不需要加入到后缀表达式 中)。
3. 读取完毕后,将栈中剩余操作符挨个出栈并加入到后缀表达式中。
根据后缀表达式求值
根据后缀表达式求值的思想如下:
1. 建立操作数栈用来保存操作数;
2. 从左至右遍历后缀表达式;
1) 如果当前字符为操作数则入栈;
2) 如果当前字符为操作符则从栈中弹出两个操作数,并用后弹出的操作数操作先弹出的操作数(读者可以想一想这句话和“用先 弹出的操作数操作 后弹出的操作数”有什么不一样,其实这和栈的FILO有关)。然后再将操作后的结果入栈。
下面是对以上表述的java代码实现:
import java.util.HashSet;
import java.util.Scanner;
import java.util.Stack;
public class InToProfix1 {
private static HashSet<Character> optr = new HashSet<Character>();
private static void initOptr() {
optr.add('+');
optr.add('-');
optr.add('*');
optr.add('/');
}
//判断字符是否为操作符
private static boolean isOptr(char c) {
if(optr.contains(c))
return true;
return false;
}
//判断字符是否为操作数
private static boolean isOperand(char c) {
if(c>='0'&&c<='9')
return true;
return false;
}
//得到当前操作符的优先级
private static int priority(char c) {
switch(c) {
case'*':
case'/':
return 3;
case'+':
case'-':
return 2;
case'(':
return 1;
default:{
System.out.println("输入错误!");
return 0;
}
}
}
//用后缀表达式求值
public static int numberCalculate(String profixExpr) {
Stack<Integer> count = new Stack<Integer>();
char c;
int number1,number2;
for(int i = 0; i < profixExpr.length(); i++) {
if(isOperand(c = profixExpr.charAt(i)))
count.push(c - '0');
else {
number2 = count.pop();
number1 = count.pop();
switch(c) {
case'+':
count.push(number1 + number2);
break;
case'-':
count.push(number1 - number2);
break;
case'*':
count.push(number1 * number2);
break;
case'/':
count.push(number1 / number2);
break;
}
}
}
return count.pop();
}
public static void main(String[] args) {
initOptr();
Scanner scan = new Scanner(System.in);
String expression = scan.nextLine(); //输入的表达式
StringBuilder profixExpr = new StringBuilder(); //保存已将建立的后缀表达式
Stack<Character> optr = new Stack<Character>(); //操作符栈
char c; //输入表达式某个位置的字符
char pop; //运算符栈中弹出的字符
for(int i = 0; i < expression.length(); i++) {
c = expression.charAt(i);
//如果当前字符为操作数
if(isOperand(c)) {
profixExpr.append(c);
}
//如果当前字符为操作符
else if(isOptr(c)) {
if(optr.isEmpty())
optr.push(c);
else {
while(true) {
if(optr.isEmpty() || priority(optr.peek()) < priority(c))
break;
pop = optr.pop();
profixExpr.append(pop);
}
optr.push(c);
}
}
//如果当前字符为‘(’
else if('(' == c) {
optr.push(c);
}
//如果当前字符为‘)’
else if(')' == c) {
while((pop = optr.pop() ) != '(')
profixExpr.append(pop);
}
else
System.out.println("输入得表达式有错误:=======>"+c);
}
while(!optr.isEmpty())
profixExpr.append(optr.pop());
System.out.println("转换后的后缀表达式为:"+profixExpr);
System.out.println("后缀表达式计算的结果为:"+numberCalculate(profixExpr.toString()));
}
}
输入一个表达式,测试结果如下:
其实,数学表达式的求值问题也可以先转为前缀表达式(逆波兰式),然后利用前缀表达式求值,并且各种表达式之间也可以相互转换,有兴趣的读者可以关注博主的其它文章。由于博主水平有限,文章中难免有不足之处,希望各位读者谅解并及时反馈,以便于博主及时更正。