文章目录
中缀表达式:就是常见的运算表达式:如"3+52","4-(23)+9"
后缀表达式(逆波兰表达式):不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。
为什么要将看似简单的中缀表达式转换为复杂的逆波兰式?原因就在于这个简单是相对人类的思维结构来说的,对计算机而言中序表达式是非常复杂的结构。相对的,逆波兰式在计算机看来却是比较简单易懂的结构。因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。 ------------来自[百度百科 逆波兰式]
中缀表达式转成后缀表达式:
先初始化两个栈,栈s1(用来存储后缀表达式的结果)和栈s2(用来存储每次遍历中缀表达式所得到的字符串)
从左到右一次扫描中缀表达式,可能会遇到三种情况:
- 遇到到操作数:直接将操作数压入s1中
- 遇到运算符:
1)若s2为空,或者s2的栈顶元素为 ‘(’ ,直接压入s2中
2)否则,若当前运算符的优先级高于s2栈顶的运算符的优先级,直接将当前的运算符压入s2中
3)否则,若当前运算符的优先级与s2栈顶的运算符的优先级相等,则将s2栈顶的运算符弹出并压入s1中,再次将当前的运算符与s2的栈顶运算符进行比较,继续执行步骤2。 - 遇到括号:
1)如果是 ‘(’ , 直接压入s2中
2)如果是 ‘)’ ,则一次弹出s2的栈顶元素并将其压入s1中,直到遇到 ‘(’ 为止,然后将这对括号丢弃 - 最后将s1中的元素依次弹出的结果逆序输出就是中缀表达式所对应的后缀表达式了。(其实在真正操作的过程中,s1是一个ArrayList类型)
图示:
最终,我们将s1的全部元素依次弹出,并逆序输出,得到后缀表达式:846+2/-9+ 通过图解我们可以知道:在转换的过程中,栈s1从来就没有进行过出栈操作,它自始至终只有压栈操作,而且,最后还要对其进行出栈,逆序输出的操作,因此,基于这两个原因,我们就**可以把s1初始化为一个ArrayList容器来存储逆波兰表达式的结果**。此时s1存储的顺序就是和逆波兰表达式里字符的顺序是一致的,不需要再将其进行逆序输出。
通过后缀表达式来计算结果:
从左到右扫描逆波兰表达式,遇到数字就将其入数栈,遇到符号就将栈顶的两个元素弹出,并进行运算,注意:运算的顺序为:后弹出的数字 ±*/ 先弹出的数字,运算得到的结果再入栈,数栈中的最后一个结果就是最终的结果
代码实现:
在整个的实现过程中,中缀表达式也好,逆波兰表达式也好,它们都是以字符串类型作为参数传入到方法中,由于我们需要对它们进行扫描,但是对字符串进行扫描的话操作起来又不方便(ch = String str.substring(index, index+1, charAt(0)) ),因此我们就可以将传入的中缀表达式字符串和后缀表达式字符串转换成用List容器来存储,这样一来就可以方便地对它们进行遍历(增强for循环)。
将中缀表达式存入LIst:
public static List<String> toInfixExpressionList(String s) {
List<String> list = new ArrayList<String>();
int i = 0; //定义指向中缀表达式的的指针
char c; //用于存放每次遍历到的字符
String str = ""; //用于多位操作数的拼接
do {
if((c=s.charAt(i)) < 48||(c=s.charAt(i)) > 57) { //遍历的字符是运算符
list.add(c+"");
i++;
}else {
str = "";
while(i<s.length()&&(c=s.charAt(i)) >= 48&&(c=s.charAt(i)) <= 57) {//'0'的ASII值为48 '9'的ASII值为57
str += c;//拼接多位操作数
i++;
}
list.add(str);
}
}while(i<s.length());
return list;
}
将中缀表达式的List转换成逆波兰表达式的List:
public static List<String> parseSuffixExpressionList(List<String> ls){
Stack<String> s1 = new Stack<String>();
//因为s2这个用于存储临时字符的栈只是执行了压栈操作,从来没有进行出栈操,
//并且,我们还要逆序输出s2中的元素,因此可以用ArrayList来代替s2
//Stack<String> s2 = new Stack<String>();
ArrayList<String> s2 = new ArrayList<String>();
for(String item:ls) {
if(item.matches("\\d+")) {//正则表达式,用来存储多位数
s2.add(item);
} else if(item.equals("(")) {
s1.push(item);
} else if(item.equals(")")) {
while(!s1.peek().equals("(")) {
s2.add(s1.pop());
}
s1.pop(); //将"("进行出栈操作
}else {//当前的字符串代表运算符时:
while(s1.size()!=0 && Operation.getValue(item) <= Operation.getValue(s1.peek())) {
s2.add(s1.pop());
}
//记得要将当前的运算符进行添加操作
s1.push(item);
}
}
//将s1中剩余的数据添加到s2中
while(s1.size()!=0) {
s2.add(s1.pop());
}
return s2;
}
完成对逆波兰表达式的计算:
public static int caculate(List<String> ls) {
Stack<String> stack = new Stack<String>();
for(String ele:ls) {
if(ele.matches("\\d+")) {
stack.push(ele);
}else {
int res = 0;
int num1 = Integer.parseInt(stack.pop());
int num2 = Integer.parseInt(stack.pop());
switch(ele) {
case "+":
res = num2 + num1;
break;
case "-":
res = num2 - num1;
break;
case "*":
res = num2 * num1;
break;
case "/":
res = num2 / num1;
break;
default:
throw new RuntimeException("逆波兰表达式中的运算符号有误!");
}
stack.push(res+"");//定义的栈stack中的元素是String类型,而res是int类型,因此需要进行类型转换
}
}
return Integer.parseInt(stack.pop());
}
定义一个类,可以返回运算符的优先顺序:
class Operation{
private static byte ADD = 1;
private static byte SUB = 1;
private static byte MUL = 2;
private static byte DIV = 2;
public static byte getValue(String oper) {
byte res = 0;
switch (oper) {
case "+":
res = ADD;
break;
case "-":
res = SUB;
break;
case "*":
res = MUL;
break;
case "/":
res = DIV;
break;
default :
System.out.println("运算符不是 +-*/");
break;
}
return res;
}
}
测试代码:
public static void main(String[] args) {
String expression = "8-(4+6)/2+9";
List<String> InfixExpression = PolanNotation.toInfixExpressionList(expression);
System.out.println(InfixExpression);
List<String> suffixExpression1 = PolanNotation.parseSuffixExpressionList(InfixExpression);
System.out.println(suffixExpression1);
System.out.printf("%s = %d\n", expression, PolanNotation.caculate(suffixExpression1));
}