前言
前面实现了一个中缀表达式计算器:数据结构 - 栈、栈实现计算器
中缀表达式是我们平常书写的表达式1+((2+3)*4)-5
,但不符合计算机的运算,所以我们设计计算器的时候异常的复杂,设计了数栈、符号栈以及多种规则才实现了简单的表达式计算
后缀表达式1 2 3 + 4 * + 5 -
更符合计算机的运算,设计一个后缀表达式计算器
Java实现后缀表达式计算器
对于提供的一个后缀表达式3 4 + 5 * 6 -
(默认空格分开),我们如何计算?
- 先把表达式分割成一个list
[3, 4, +, 5, *, 6, -]
- 后缀表达式计算:
1.从左至右扫描,扫描到数字,将3、4压入栈
2.当碰到运算符,3、4出栈,计算,将结果7压入栈
3.扫描到数字5入栈
4.扫描到运算符*,将7、5出栈,计算,将25入栈
5.扫描到数字6入栈
6.扫描的运算符-,将25、6出栈,根据出栈顺序25-6,得到最终结果,入栈
相对与中缀表达式的计算,后缀表达式简单多了:只需设置一个栈,将表达式元素一个个压入栈中,数字直接压入,运算符就弹出两个数字计算后将结果压入栈
package com.company.polandNotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* @author zfk
*
* 逆波兰表达式计算器
*/
public class PolandNotation {
public static void main(String[] args) {
//逆波兰表达式(3+4)*5-6 => 34+5*6-
//为了方便,用空格分开
String suffixExpression = "3 4 + 5 * 6 -";
List<String> list = getListString(suffixExpression);
System.out.println(list);
int result = calculate(list);
System.out.println("表达式:"+suffixExpression+" = "+result);
}
/**
* @param suffixExpression 逆波兰表达式
* @return
*
*/
public static List<String> getListString(String suffixExpression){
//将表达式分割
String[] expression = suffixExpression.split(" ");
ArrayList<String> list = new ArrayList<>();
for (String e : expression){
if (e == " "){
continue;
}
list.add(e);
}
return list;
}
/**
* @param list 表达式字符列表[3, 4, +, 5, *, 6, -]
* @return 表达式结果
* 步骤:1.从左至右扫描,扫描到数字,将3、4压入栈
* 2.当碰到运算符,3、4出栈,计算,将结果7压入栈
* 3.扫描到数字5入栈
* 4.扫描到运算符*,将7、5出栈,计算,将25入栈
* 5.扫描到数字6入栈
* 6.扫描的运算符-,将25、6出栈,根据出栈顺序25-6,得到最终结果,入栈
*/
public static int calculate(List<String> list){
//创建一个栈
Stack<String> stack = new Stack<String>();
//遍历list
for (String item : list){
//正则表达式处理
//如果匹配的是多位数
if (item.matches("\\d+")){
//入栈
stack.push(item);
}
//如果是运算符
else {
int str1 = Integer.parseInt(stack.pop());
int str2 = Integer.parseInt(stack.pop());
int result = 0;
switch (item){
case "+":
result = str1 + str2;
break;
case "-":
//后弹出栈的数据减去先弹出的数据
result = str2 - str1;
break;
case "*":
result = str1 * str2;
break;
case "/":
//后弹出栈的数据除先弹出的数据
result = str2 / str1;
break;
default:
System.out.println("请输入【+ - * /】");
break;
}
stack.push(String.valueOf(result));
}
}
//最后留着栈里的数据就是表达式运算结果
return Integer.parseInt(stack.pop());
}
}
当然,正常人写表达式时不会写后缀表达式,而是书写中缀表达式,这就需要我们来完成中缀表达式转后缀表达式1+((2+3)*4)-5
=》1 2 3 + 4 * + 5 -
中缀表达式转后缀表达式
1+((2+3)*4)-5
=》1 2 3 + 4 * + 5 -
规则是这样的:
- 创建两个栈:s1符号栈,s2中间结果栈(也可以用List简化),从左至右遍历
- 当遍历到数字时,直接压入s2栈
- 当遍历到括号:左括号,直接压入s1栈;右括号,判断s1栈顶元素,如果是运算符,弹出压入s2,然后继续判断栈顶,直到碰到栈顶是左括号,消除这对括号(弹出左括号)
- 当遍历到运算符:
(1)当s1栈为空或者栈顶为左括号,直接入栈s1
(2)当s1栈顶为运算符,判断优先级,当栈顶运算符优先级大于等于当前运算符,将s1栈顶运算符弹出加入到s2,继续优先级判断
(3)当栈顶运算符优先级小于当前运算符,将当前运算符压入s1栈 - 当遍历完表达式,将s1栈的所有元素弹出加入s2栈
- s2栈的结果逆序输出即为后缀表达式(当前设置为List,直接遍历即可)
Java实现中缀表达式转后缀表达式:
先设置一个优先级类(仅完成加减乘除)
/**
* 返回 运算符 对应的优先级
*/
class Operation{
private static int ADD = 1;
private static int SUB = 1;
private static int MUL = 2;
private static int DIV = 2;
/**
* @param operation 传入的符号
* @return 优先级数字
*/
public static int getValue(String operation){
int result = 0;
switch (operation){
case "+":
result = ADD;
break;
case "-":
result = SUB;
break;
case "*":
result = MUL;
break;
case "/":
result = DIV;
break;
default:
break;
}
return result;
}
}
在前面的PolandNotation 类中添加方法:
- 将中缀表达式分割转为List列表
- 将中缀表达式List转为后缀表达式List
/**
* @param expression 表达式
* @return
* 将表达式转成ArrayList
* //(3+4)*5-6 => [(, 3, +, 4, ), *, 5, -]
*/
public static List<String> toInfixExpression(String expression){
//定义一个list存放数据
List<String> list = new ArrayList<>();
//指针:遍历表达式
int index = 0;
//对多位数的拼接
String str;
//每遍历一个字符,就放入c
char c;
while (index < expression.length()){
//如果c是一个非数字,直接加入list
if ((c = expression.charAt(index)) < 48 || (c = expression.charAt(index)) > 57){
list.add(String.valueOf(c));
//指针后移
index++;
}
//如果c是一个数字,需要拼接(考虑多位数)
else {
//将str置成空串
str = "";
//判断c后续的字符是否为数字
while (index < expression.length() && (c = expression.charAt(index)) >= 48 && (c = expression.charAt(index)) <= 57){
//是数字,即多位数,需要拼接
str += c;
index++;
}
list.add(str);
}
}
return list;
}
/**
* @param list 表达式转换的list
* @return
* 将List转换为后缀表达式
*/
public static List<String> parseSuffixExpression(List<String> list){
//符号栈
Stack<String> s1 = new Stack<>();
//存储中间结果栈,因为在过程中并没有pop操作,且结果需要逆序输出,可以使用List简化
List<String> s2 = new ArrayList<>();
//遍历list
for (String item : list){
//正则表达式:如果是一个数字
if (item.matches("\\d+")){
s2.add(item);
}
else {
switch (item){
case "(":
//如果是左括号,直接压入s1栈
s1.push(item);
break;
case ")":
//如果是右括号,依次弹出s1栈顶的运算符,并压入s2
// 直到遇到左括号为止,将这对括号丢弃
while (!s1.peek().equals("(")){
s2.add(s1.pop());
}
//弹出左括号
s1.pop();
break;
case "+":
case "-":
case "*":
case "/":
//当item的优先级小于等于s1栈顶运算符,将s1栈顶运算符弹出加入s2
//再次回到优先级判断 与s1中新的栈顶运算符比较
while (s1.size() != 0 && Operation.getValue(item) <= Operation.getValue(s1.peek())){
s2.add(s1.pop());
}
//将item压入s1栈
s1.push(item);
break;
default:
System.out.println("请输入正确的表达式符号:【+ - * / ( )】");
break;
}
}
}
s2.add(s1.pop());
return s2;
}
测试(在前面的main方法中):
String express = "1+((2+3)*4)-5";
List<String> infixExpression = toInfixExpression(express);
System.out.println("中缀表达式对应的List="+infixExpression);
List<String> suffixExpression1 = parseSuffixExpression(infixExpression);
System.out.println("后缀表达式对应的List="+suffixExpression1);
完成:
到了这里,逆波兰计算器就完成了,将上面的两个拼接
main方法:
public static void main(String[] args) {
String express = "1+((2+3)*4)-5";
List<String> infixExpression = toInfixExpression(express);
System.out.println("中缀表达式对应的List="+infixExpression);
List<String> suffixExpression = parseSuffixExpression(infixExpression);
System.out.println("后缀表达式对应的List="+suffixExpression);
int result = calculate(suffixExpression);
System.out.println("表达式:"+express+" = "+result);
}
总结
- 后缀表达式的计算:从左至右扫描表达式,设置一个栈,数字直接加入栈,扫描到运算符就两次弹出栈的栈顶(两个数字)进行运算,将结果压入栈,直到最终结果
- 中缀表达式转后缀表达式:
设置两个栈:符号栈s1、中间结果栈s2;
从左至右扫描表达式,碰到数字直接压入s2栈;
当碰到括号,左括号直接压入s1,扫描到右括号,判断s1栈顶,当栈顶是运算符,弹出加入s2,继续判断栈顶直到碰到左括号,弹出左括号,结束;
当碰到运算符,如果栈为空或者栈顶为左括号,直接压入栈,如果栈顶为运算符,判断优先级,如果栈顶运算符优先级大于等于当前运算符,弹出栈顶运算符到s2,进行判断,直到栈顶运算符优先级小于当前运算符或者栈为空或者栈顶为左括号,当前运算符压入s1;
扫描完表达式,将s1栈中的符号弹出加入s2,得到的s2逆序输出就是后缀表达式