1.逆波兰表达式(后缀表达式)的计算过程:
- 例如:“(3+4)*5-6”对应的逆波兰表达式为“3 4 + 5 * 6 -”,针对后缀表达式使用栈求值的步骤:
- 从左至右扫描,将3和4压入堆栈;
- 遇到+运算符,弹出栈顶元素4和次顶元素3,计算3+4的值,得7后将7入栈;
- 将5入栈;
- 遇到*运算符,弹出7和5,计算乘积为35压入栈中;
- 将6入栈;
- 遇到-运算符,弹出栈顶元素和次顶元素,计算次顶元素-栈顶元素(35-6=29),即结果为29;
2.思路分析:
- 输入是String格式的后缀表达式,需要将后缀表达式转换为对应的list,方便遍历表达式中的元素;
- 对List进行遍历,配合栈,完成对逆波兰表达式的计算;
3.逆波兰计算器(只处理整数)代码实现:
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
//定义逆波兰表达式
//(30+4)×5-26 => 30 4 + 5 × 26 -,使用空格隔开
//4*5-8+60+8/2 => 4 5 * 8 - 60 + 8 + 2 /
String suffixExpression="4 5 * 8 - 60 + 8 2 / +";
//1.先将"3 4 + 5 × 6 -"放到ArrayList中;
//2.将ArrayList传递给方法,遍历数组,配合栈完成计算
List<String> rpnList=getListString(suffixExpression);
System.out.println("rpnList= "+rpnList);
int res=calcute(rpnList);
System.out.println("计算的结果是= "+res);
}
//将逆波兰表达式依次将数据和运算符放入到ArrayList中
public static List<String> getListString(String suffixExpression){
String[] split=suffixExpression.split(" ");
List<String> list=new ArrayList<String>();
for(String ele:split) {
list.add(ele);
}
return list;
}
//完成对逆波兰表达式的运算,对List进行遍历
public static int calcute(List<String> ls) {
//创建栈
Stack<String> stack = new Stack<String>();
//遍历ls
for(String item:ls) {
//使用正则表达式取出数
if(item.matches("\\d+")) {//匹配多位数
stack.push(item);
}else {
//若不是数字,则pop出两个数,再入栈
int num2=Integer.parseInt(stack.pop());
int num1=Integer.parseInt(stack.pop());
int res=0;
if(item.equals("+")) {
res=num1+num2;
}else if(item.equals("-")){
res=num1-num2;//注意顺序
}else if(item.equals("*")) {
res=num1*num2;
}else if(item.equals("/")) {
res=num1/num2;
}else {
throw new RuntimeException("运算符有误");
}
//把结果res入栈
stack.push(""+res);//将整型转为string
}
}
//最后留在stack中的数据是运算结果
return Integer.parseInt(stack.pop());
}
}
4.中缀表达式转换为后缀表达式
- 初始化两个栈:运算符栈s1,存储中间结果的栈s2;
- 从左至右扫描中缀表达式;
- 遇到操作数时,将其压入s2;
- 遇到运算符时,比较其与s1栈顶运算符的优先级:
- 如果s1为空,或栈顶运算符为左括号‘(’,则直接将运算符压入栈s1;
- 否则,若优先级比栈顶运算符的高,也将运算符压入栈s1;
- 否则,将s1栈顶的运算符弹出并压入到s2中,将遍历到的运算符继续与s1栈中新的栈顶运算符相比较;
- 遇到括号时:
- 如果是左括号‘(’,则直接压入栈s1;
- 如果是右括号‘)’,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃;
- 重复2到5,直到表达式的最右边;
- 将s1中剩余的运算符依次弹出并压入s2;
- 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式;
5.中缀表达式转后缀表达式代码实现
- 输入是String形式的表达式,将其首先转换为中缀表达式对应的List,方便遍历;
//方法:将中缀表达式转化为对应的list
public static List<String> toInfixExpressionList(String s){
List<String> ls=new ArrayList<String>();
int i=0;//用于遍历中缀表达式字符串s的指针
String str;//用于对多位数拼接
char c;//每遍历到一个字符,放入到c
do {
//如果c是非数字,加入到ls中
if((c=s.charAt(i))<48 || (c=s.charAt(i))>57){
ls.add(""+c);
i++;//i后移
}else {//数字考虑多位数的问题
str="";//将str置空
while(i<s.length() && (c=s.charAt(i))>=48 && (c=s.charAt(i))<=57 ) {
str+=c;//拼接
i++;
}
ls.add(str);
}
}while(i<s.length());
return ls;//返回数组
}
- 定义运算符优先级高低的Operation类,可以返回一个运算符对应的优先级数字
class Operation{
private static int ADD=1;
private static int SUB=1;
private static int MUL=2;
private static int DIV=2;
//方法,返回对应的优先级数字
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;
case "(":
break;
case ")":
break;
default:
throw new RuntimeException("运算符输入错误");
}
return result;
}
}
- 将得到的中缀表达式对应的List转换为后缀表达式对应的List
public static List<String> parseSuffixExpressionList(List<String> ls){
//定义两个栈
Stack<String> s1=new Stack<String>();//符号栈
//s2在算法中没有pop操作,且需要逆序输出,故可以不用s2栈而用ArrayList更简单操作
// Stack<String> s2=new Stack<String>();//储存中间结果的栈
List<String> s2=new ArrayList<String>();//存储中间结果的List
//遍历ls
for(String item:ls) {
if(item.matches("\\d+")) {//如果是数,加入s2
s2.add(item);
}else if(item.equals("(")) {//左括号直接入栈s1
s1.push(item);
}else if(item.equals(")")) {
//如果是右括号,依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将一对括号丢弃
while(!s1.peek().equals("(")) {
s2.add(s1.pop());
}
s1.pop();//重要,将"("丢弃,消除括号
}else {
//若item的优先级小于等于s1栈顶运算符的优先级,将s1栈顶的运算符弹出并加入到s2中,再将item与s1栈顶运算符比较
//问题:缺少比较优先级高低的方法
while(s1.size()!=0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)) {
s2.add(s1.pop());
}
//最后将item压入s1栈
s1.push(item);
}
}
//将s1中剩余的运算符加入到s2中
while(s1.size()!=0) {
s2.add(s1.pop());
}
return s2;//s2是list,输出的顺序就是存入的顺序,即后缀表达式的正确顺序
}
- main方法测试:
public static void main(String[] args) {
String expression="1+((2+3)*4+5)/5-5";
List<String> infixExpressionList=toInfixExpressionList(expression);
System.out.println("中缀表达式转为为list= "+infixExpressionList);
List<String> suffixExpressionList=parseSuffixExpressionList(infixExpressionList)
System.out.println("后缀表达式对应的list= "+suffixExpressionList);
System.out.printf("expression: %s=%d",expression,calcute(suffixExpressionList));
System.out.println();
}
6.逆波兰计算器的完整版
package stack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;
public class ReversePolandMultiCalc {
//匹配 + - * / ()运算符
static final String SYMBOL="\\+|-|\\*|/|\\(|\\)";
static final String LEFT="(";
static final String RIGHT=")";
static final String ADD="+";
static final String MINUS="-";
static final String TIMES="*";
static final String DIVISION="/";
static final int LEVEL_01=1;
static final int LEVEL_02=2;
static final int LEVEL_HIGH=Integer.MAX_VALUE;
static Stack<String> stack=new Stack<>();
static List<String> data=Collections.synchronizedList(new ArrayList<String>());
//方法:去除所有空白符
public static String replaceAllBlank(String s) {
//\\s+匹配任何空白字符,包括空格、制表符、换页符等
return s.replace("\\s+", "");
}
//方法:判断是不是数字 int double long float
public static boolean isNumber(String s) {
Pattern pattern=Pattern.compile("^[-\\+]?[.\\d]*$");
return pattern.matcher(s).matches();
}
//方法:判断是否是运算符
public static boolean isSymbol(String s) {
return s.matches(SYMBOL);
}
//方法:匹配运算等级
public static int calcLevle(String s) {
if("+".equals(s)||"-".equals(s)) {
return LEVEL_01;
}else if("*".equals(s)||"/".equals(s)) {
return LEVEL_02;
}
return LEVEL_HIGH;
}
//方法:匹配
public static List<String> doMatch(String s) throws Exception{
if(s==null||"".equals(s.trim())) {
throw new RuntimeException("data is empty");
}
if(!isNumber(s.charAt(0)+"")) {
throw new RuntimeException("data is illeagle,start not with a number");
}
s=replaceAllBlank(s);
System.out.println("表达式字符串:"+s);
String each;
int start=0;
for(int i=0;i<s.length();i++) {
if(isSymbol(s.charAt(i)+"")) {
each=s.charAt(i)+"";
//栈为空
if(stack.isEmpty() || LEFT.equals(each) ||((calcLevle(each)>calcLevle(stack.peek())) && calcLevle(each)<LEVEL_HIGH)){
stack.push(each);
}else if(!stack.isEmpty() && calcLevle(each)<=calcLevle(stack.peek())) {
//栈非空,操作符优先级小于等于栈顶优先级时出栈入列,直到栈为空,最后操作符入栈
while(!stack.isEmpty() && calcLevle(each)<=calcLevle(stack.peek())) {
if(calcLevle(stack.peek())==LEVEL_HIGH) {
break;
}
data.add(stack.pop());
}
stack.push(each);
}else if(RIGHT.equals(each)) {
//右括号,依次出栈直到空栈或遇到第一个左括号操作符,此时左括号出栈
while(!stack.isEmpty() && LEVEL_HIGH>=calcLevle(stack.peek())) {
if(LEVEL_HIGH==calcLevle(stack.peek())) {
stack.pop();
break;
}
data.add(stack.pop());
}
}
start=i;//前一个运算符的位置
}else if(i==s.length()-1 ||isSymbol(s.charAt(i+1)+"")) {
each=start==0?s.substring(start,i+1):s.substring(start+1,i+1);
if(isNumber(each)) {
data.add(each);
continue;
}
throw new RuntimeException("data not match number");
}
}
//如果栈中还有元素,元素需要依次出栈入列
Collections.reverse(stack);
data.addAll(new ArrayList<>(stack));
System.out.println(data);
return data;
}
//方法:运算
public static Double doTheMath(String s1,String s2,String symbol) {
Double result;
switch(symbol) {
case ADD:
result=Double.valueOf(s1)+Double.valueOf(s2);
break;
case MINUS:
result=Double.valueOf(s1)-Double.valueOf(s2);
break;
case TIMES:
result=Double.valueOf(s1)*Double.valueOf(s2);
break;
case DIVISION:
result=Double.valueOf(s1)/Double.valueOf(s2);
break;
default:
result=null;
}
return result;
}
//方法:计算结果
public static Double doCalc(List<String> list) {
Double d=0d;
if(list==null||list.isEmpty()) {
return null;
}
if(list.size()==1) {
System.out.println(list);
d=Double.valueOf(list.get(0));
return d;
}
ArrayList<String> list1=new ArrayList<>();
for(int i=0;i<list.size();i++) {
list1.add(list.get(i));
if(isSymbol(list.get(i))) {
Double d1=doTheMath(list.get(i-2),list.get(i-1),list.get(i));
list1.remove(i);
list1.remove(i-1);
list1.set(i-2, d1+"");
list1.addAll(list.subList(i+1, list.size()));
break;
}
}
doCalc(list1);
return d;
}
public static void main(String[] args) {
String math="9+(3-1)*3+10/2";
String math1="12.8+(2-3.55)*4+10/5.0";
try {
doCalc(doMatch(math1));
} catch (Exception e) {
e.printStackTrace();
}
}
}