在进行计算时,计算机与人不一样。相比较中缀表达式,它采用后缀表达式计算起来要明显方便许多。
下面就用 java + 栈 ,实现:对于输入的算式,转化为对应的后缀表达式,并且计算出算式值 的功能:
思路梳理
处理的主要思路为:
1、将输入的expression(中缀表达式)进行转换 => 中缀表达式对应的ArrayList类的list,
便于在由中缀表达式转换为后缀表达式的过程中,进行遍历操作:
1.1 需要注意的是:
* 1) 在转换的过程中,分为两种情况:
* 是运算符,直接add到list中;
* 是数字,可能是多位数,需要确定数字的所有位数之后,再add到list中
* 注意这里的循环控制条件中,不同模块之间的关系:
* ① 下标(索引)不越界
* >是运算符的情况,不需要加上这个,是因为只是if语句,不需要循环,外层的do-while循环就可以帮助实现–控制下标不越界
* >但是,是数字的情况,因为可能是多位数,所以有一个内层while循环;此时,就需要加上一个index < s.length()的条件控制下标不越界
* ② s.charAt(index)存储的是一个数字----ASCII值位于48~57之间,两点必须同时满足
*/
* 2) 输入的表达式中,数字 很可能是多位数,需要使用一个循环,确定多位数中的所有位数
* ----设置一个用于拼接的字符串str(初始值为 空串),在确定一个数的某一位上仍是数字时,将该位 拼接到str上
* 在一个数的位数的确定过程中,如果某一位上是 运算符,就将之前的str,add到list中去
2、将中缀表达式对应的list转换为对应的后缀表达式
- 2.1 理论上需要两个栈:一个符号栈s1,一个用于存储确定的运算顺序的栈s2;并且最后s2栈的逆序输出结果就是逆波兰表达式(后缀表达式)
* 但是,因为s2在整个过程中,并不进行pop()操作;并且最终的逆波兰表达式 是 s2中的元素逆序输出的结果
* ----实际上,可以使用ArrayList代替stack,来作为s2对象真实的数据结构
* 2.2 在判断读取到的c(list中的元素),是数字还是符号的过程中,
* 可以选择正则表达式:matches("\d+")----表示:多位数
* 2.3 比较运算符优先级的方式,需要自己来实现
* 可以来定义一个类priority,来实现 确定某个运算符的优先级
①实现将中缀表达式对应的list转换为对应的后缀表达式,这里的算法实现比较复杂:
- 1、需要两个栈:一个符号栈s1,一个用于存储确定的运算顺序的栈s2;并且最后s2栈的逆序输出结果就是逆波兰表达式(后缀表达式)
* 2、从左往右遍历list(存储的是expression的中缀序列)
* 3、如果读取到的c是 数字,add进s2;
* 4、如果读取到的c是 运算符,分为以下几种情况:
* 1> 如果s1是空栈,or s1的栈顶元素是 左括号“(”,将c push进s1;
* 2> 如果c的优先级 大于 s1的栈顶元素的优先级,那么将c push进s1;
* 3> 如果c的优先级 小于或者等于 s1的栈顶元素的优先级,那么,将s1 的栈顶元素出栈,add 进s2中去;
* 并且,再次转回4-1>,重新判断c 与 s1的状态;
* 5、 如果读取到的c是 括号,分为以下情况
* 1> 如果c是左括号“(”,那么,直接push进入s1;
* 2> 如果c是右括号“)”,那么,需要将s1的栈顶元素pop,并且add进入s2,直至遇见左括号“(”为止
* ----这也是消除一对左括号的方法;
* 6、重复上述2~5步骤,直至将list的最后一个元素读取结束
* 7、因为s2是采用的ArrayList,所以,最终s2中存储的,就是转换之后的 逆波兰表达式
②在实现时的注意事项:
- 将中缀表达式对应的list转换为对应的后缀表达式
* 2.1 理论上需要两个栈:一个符号栈s1,一个用于存储确定的运算顺序的栈s2;并且最后s2栈的逆序输出结果就是逆波兰表达式(后缀表达式)
* 但是,因为s2在整个过程中,并不进行pop()操作;并且最终的逆波兰表达式 是 s2中的元素逆序输出的结果
* ----实际上,可以使用ArrayList代替stack,来作为s2对象真实的数据结构
* 2.2 在判断读取到的c(list中的元素),是数字还是符号的过程中,
* 可以选择正则表达式:matches("\d+")----表示:多位数
* 2.3 比较运算符优先级的方式,需要自己来实现
* 可以来定义一个类priority,来实现 确定某个运算符的优先级
*/
3、对于expression调整得到的list,进行遍历,并且结合堆栈,完成运算calculation
- 3.1 注意的是:在进行判断 集合中的元素是数字(包含多位数的情况)还是运算符的时候,是可以使用正则表达式
* 对应的是string类的matches(regex)方法,其中的regex表示:正则表达式
代码实现
1、实现主要功能的类PolandNotation( 包含mian() )
class PolandNotation {
public static void main(String[] args) {
//创建一个string对象,存储要进行计算的表达式
String expression = "1+((2*4+3)*4)-5";
//将输入的expression(中缀表达式)进行转换 => 中缀表达式对应的ArrayList类的list
List<String> list = infixException(expression);
// System.out.println(list);
List<String> suffixExpression = suffixExpression(list);
// System.out.println(suffixExpression);
int res = calcultation(suffixExpression);
System.out.println(expression + " 的计算结果为: " + res);
}
//将输入的expression(中缀表达式)进行转换 => 中缀表达式对应的ArrayList类的list
public static List<String> infixException(String s){
//创建一个ArrayList类的对象,存储中缀表达式对应的list
ArrayList<String> list = new ArrayList<String>();
//对于s中的元素进行遍历,来达到转换的目的
char c;//暂存 从s中读取到的,需要进行判断的字符
String str;//用于:在读取数的过程中,暂存多位数的string形式的变量
int index = 0;//表示s中,表示当前 遍历到的索引下标
do {
c = s.charAt(index);
//如果c是运算符,那么c对应的ASCII值,应该就不在[48,57]的范围之内。注意:判断是不是运算符时,对于ASCII的范围,一定不包含 == 的情况
if(c > 57 || c < 48) {
list.add("" + c);//因为 c是char型得到,所以应该转换为string类型(拼接一个 空串"")
index ++ ;//下一个的索引值
}else {
//c是数字的情况:每一位上面的数字就一定要在[48,57]的范围之内,可能是多位数,需要确定数字的所有位数之后,再add到list中
str = "";//初始化str
while(index < s.length() && (s.charAt(index) <= 57 && s.charAt(index) >= 48)) {
c = s.charAt(index);
/*
* 注意,在内层的while循环处:c = s.charAt(index),这个赋值是一定要存在的。
* 虽然在if的上一句也存在这个赋值语句,但是,只对内层while循环的第一次进行有用。
* 如果不在内层的while循环处,加上一个这样的赋值语句,就会发现,c的值,一直是1
*/
str += c;
index ++;//指向下一个索引
}
list.add(str);
}
}while(index < s.length());
return list;
}
//将中缀表达式对应的list转换为对应的后缀表达式
public static List<String> suffixExpression(List<String> list) {
//创建s1、s2
Stack<String> s1 = new Stack<String>();
ArrayList<String> s2 = new ArrayList<String>();
//循环遍历list中的元素
for(String item : list) {
// System.out.println("item=" + item);
if(item.matches("\\d+")) {
//2、item如果是数字,直接add进s2
s2.add(item);
}else if(item.equals("(")){
//5.1 是左括号,直接push进入s1
s1.push(item);
}else if(item.equals(")")) {
/*
* 5.2 是右括号“)”,那么,需要将s1的栈顶元素pop,并且add进入s2,直至遇见左括号“(”为止
* ----这也是消除一对左括号的方法;
*/
while(!(s1.peek().equals("("))) {//直至遇见左括号“(”
s2.add(s1.pop());
}
/*
* 注意:这一步很重要!
* 因为在遇到右括号时,有两项任务是要完成的:
* >需要将s1的栈顶元素pop,并且add进入s2,直至遇见左括号“(”为止
* >消除一对左括号:----当对s1执行pop操作,直到遇到s1.peek(s1的栈顶元素)为左括号时,应该停止s2.add(s1.pop())操作,并且执行s1.pop()----将左括号从s1中pop出来,但是 不add进s2,:实现丢弃一对括号
*/
s1.pop();//很重要!!!
}else {
/*
*是4、的所有情况,写在一起处理
* 这里在进行处理时,最简单的方法,是在这一小块时,把左括号也当做运算符,且优先级最低
* 这是因为:4.1--不论读取到的运算符是什么(+-*或除),在遇到左括号时,都进行操作s1.push()
*/
while(s1.size() > 0 && Priority.getPriority(s1.peek()) >= Priority.getPriority(item)) {
/*
* 基本上对应4.3:
* 实际上,是在item是运算符时,除了4.1、4.2之外的所有情况
*/
s2.add(s1.pop());//将s1的栈顶元素取出,add进s2中去
}
/*
* 对应4.1,4.2的情况:
* 1> 如果s1是空栈,or s1的栈顶元素是 左括号“(”,将c push进s1;
* 2> 如果c的优先级 大于 s1的栈顶元素的优先级,那么将c push进s1;
*/
s1.push(item);
}
}
//从左至右扫描结束后,将s1中的剩余元素全都pop,再add进s2
while(s1.size() > 0) {//s1中没有空
s2.add(s1.pop());
}
// System.out.println(s2);
return s2;
}
//对于expression调整得到的list,进行遍历,并且结合堆栈,完成运算calculation
public static int calcultation(List<String> ls) {
//创建一个堆栈的对象,在计算过程存储数字,也称为数栈
Stack<String> stack = new Stack<String>();
/*
* 开始对ls进行遍历,具体的操作为:
* 如果遇到 数字,就将数字压入堆栈;
* 如果遇到 运算符,就从数栈中pop()出来栈顶元素、次顶元素:num1、num2。
* 进行相应运算,并将运算的结果压入数栈
*/
for (String item : ls) {
// 如果是数字(多位数),直接入 数栈
if (item.matches("\\d+")) {// 是多位数
stack.push(item);
} else {
/*
* 是运算符
* 出栈2个元素:栈顶元素、次顶元素
*/
//定义一个变量,暂存每次计算的结果值;并在最终,将最终的结果值赋给他
int num2 = Integer.parseInt(stack.pop());// 栈顶元素
int num1 = Integer.parseInt(stack.pop());// 次顶元素
int res = 0;
// 根据当前的item的运算符类型,进行相应的计算
switch (item) {
case "+":
res = num1 + num2;
break;
case "-":
res = num1 - num2;
break;
case "*":
res = num1 * num2;
break;
case "/":
res = num1 / num2;
break;
default:
throw new RuntimeException("输入的运算符有误!");
}
stack.push(res + "");//因为res是int类型的,转换为string类型的才可以入栈
}
}
return Integer.parseInt(stack.pop());
}
}
2、在实现中缀表达式 转为对应的后缀表达式时,确定运算符优先级的类
/*
* 比较运算符优先级的方式,需要自己来实现
* 可以来定义一个类priority,来实现 确定某个运算符的优先级
*/
class Priority{
//定义几个属性(全局常量),来表示各个 运算符对应的优先级
private static final int SUM = 1;
private static final int DEC = 1;
private static final int MUL = 2;
private static final int DIV = 2;
public static int getPriority(String operation) {//传入的是一个运算符
int res = 0;//优先级默认值为0
//确定传入的运算符的优先级
switch(operation) {
case "+":
res = SUM;
break;
case "-":
res = DEC;
break;
case "*":
res = MUL;
break;
case "/":
res = DIV;
break;
case "(":
//默认左括号----“(”的优先级最低,即为res的默认值--0
break;
default:
//对应的是s1.peek().equals("(")
System.out.print("输入的运算符有误!!!" + "\t");
}
// System.out.println(operation);
return res;
}
}