“这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战”
关注我,以下内容持续更新
一 预览
| 中缀表达式 | 前缀表达式 | 后缀表达式 |
|---|---|---|
| 2*3/(2-1)+3*(4-1) | +/*23-21*3-41 | 23*21-/341-*+ |
上一篇我们介绍了数据结构与算法二:1)桟 这篇主要介绍栈的应用--逆波兰表达式,逆波兰表达式就是后缀表达式
中缀表达式对我们而言是很直观的(我们平时接触的就是这个),但计算机处理起来比较麻烦(括号、优先级之类的),前缀和后缀表达式中没有括号(因为中缀表达式转为前/后缀表达式后括号被舍弃,依旧保持正确的计算顺序),而且在计算中只需单向扫描(前缀表达式从右向左扫描,后缀表达式从左向右扫描),不需要考虑运算符的优先级。
二 后缀表达式
后缀表达式也叫逆波兰表达式,后缀表达式的运算符位于操作数之后.
举例说明:(3+4)x5-6对应的后缀表达式就是 3 4 + 5 x 6 - ; 至于后缀表达式怎么来的,后边会讲如何将中缀表达式转为后缀表达式
再比如:
| 正常的表达式 | 逆波兰表达式 |
|---|---|
| a+b | ab+ |
| a+(b-c) | abc-+ |
| a+(b-c)*d | abc-d*+ |
| a+d*(b-c) | adbc-*+ |
| a=1+3 | a13+= |
| a+b | ab+ |
1. 后缀表达式求值:
1) 计算思路
从左至右取数,直到取出一个运算符,将刚取出的紧挨着运算符的两个操作数按运算符进行计算,结果回填至运算符。重复该步骤,直到最后只剩下一个字符串则剩下的字符串即为结果。
前缀表达式的计算规则和后缀表达式相似,唯一不同的是扫描方向相反. 前序表达式是从右往左,后序表达式是从左往右.
2) 代码思路
① 计算规则
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
② 计算过程
以后缀表达式'3 4 + 5 x 6 -'为例, 求值步骤如下:
- 从左至右扫描,将3和4压入堆栈;
- 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
- 将5入栈;
- 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
- 将6入栈;
- 最后是-运算符,计算出35-6的值,即29,由此得出最终结果
后缀表达式求值代码(先把字符串转为数组)
/**
1)从左至右扫描,将3和4压入堆栈;
2)遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
3)将5入栈;
4)接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
5)将6入栈;
6)最后是-运算符,计算出35-6的值,即29,由此得出最终结果
*/
public static int calculate(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);
}
}
//最后留在stack中的数据是运算结果
return Integer.parseInt(stack.pop());
}
2. 中缀表达式转后缀表达式
步骤
中缀表达式转后缀表达式步骤
1)初始化两个栈:运算符栈s1和储存中间结果的栈s2;
2)从左至右扫描中缀表达式;
3)遇到操作数时,将其压s2;
4)遇到运算符时,比较其与s1栈顶运算符的优先级:
4.1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
4.2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
4.3)否则(优先级小于/等于栈顶运算符),将s1栈顶的运算符弹出并压入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
5)遇到括号时:
5.1) 如果是左括号“(”,则直接压入s1
5.2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
6)重复步骤2至5,直到表达式的最右边
7)将s1中剩余的运算符依次弹出并压入s2
8)依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
举例说明:将中缀表达式“1+((2+3)×4)-5”转换为后缀表达式的过程如下,因此结果为 "1 2 3 + 4 × + 5 –"
代码如下:
//将中缀表达式对应的List转为后缀表达式对应的List
//即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =》 ArrayList [1,2,3,+,4,*,+,5,–]
public static List<String> parseSuffixExpreesionList(List<String> ls){
//定义两个栈:符号栈和存储中间结果的桟
Stack<String> s1 = new Stack<String>(); // 符号栈
//说明:因为s2 这个栈,在整个转换过程中,没有pop操作,而且后面我们还需要逆序输出
//因此比较麻烦,这里我们就不用 Stack<String> 直接使用 List<String> s2
//Stack<String> s2 = new Stack<String>(); // 储存中间结果的栈s2
List<String> s2 = new ArrayList<String>(); // 储存中间结果的Lists2
//遍历ls
for(String item: ls) {
if(item.matches("\\d+")) {//匹配多位数
s2.add(item);// //如果是一个数,直接加入s2
} else if (item.equals("(")) {
s1.push(item);//如果是(,直接加入s1
} else if (item.equals(")")) {
//如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
while(!s1.peek().equals("(")) {
s2.add(s1.pop());
}
s1.pop();//!!! 将 ( 弹出 s1栈, 消除小括号
} else {
//当item的优先级小于等于s1栈顶运算符, 将s1栈顶的运算符弹出并加入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
//注意:这里有一个比较优先级高低的方法Operation.getValue()
while(s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item) ) {
s2.add(s1.pop());
}
//还需要将item压入栈
s1.push(item);
}
}
//将s1中剩余的运算符依次弹出并加入s2
while(s1.size() != 0) {
s2.add(s1.pop());
}
return s2; //注意因为是存放到List, 因此按顺序输出就是对应的后缀表达式对应的List
}
3. 后缀表达式的应用-逆波兰计算器完整代码
例如计算 1+((2+3)×4)-5
/**
实现一个逆波兰计算器
思路: 例如计算 1+((2+3)×4)-5
第一步:先将一个中缀表达式字符串转为数组(因为字符串不方便运算)
第二步:将中缀表达式数组转成后缀表达式数组:
1+((2+3)×4)-5 => 转成 1 2 3 + 4 × + 5 –
第三步:对后缀表达式求值
*/
public static void main(String[] args) {
String expression = "1+((2+3)*4)-5";//注意表达式
//第一步:先将中缀表达式的字符串转换为数组
//因为直接对str进行操作不方便,因此先将字符串转为数组
List<String> infixExpressionList = toInfixExpressionList(expression);
System.out.println("中缀表达式对应的List=" + infixExpressionList); // ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
//第二步:中缀表达式的数组转为后缀表达式的数组
List<String> suffixExpreesionList = parseSuffixExpreesionList(infixExpressionList);
System.out.println("后缀表达式对应的List" + suffixExpreesionList); //ArrayList [1,2,3,+,4,*,+,5,–]
//第三步:用后缀表达式求值
System.out.printf("expression=%d", calculate(suffixExpreesionList));
}
//第一步:将中缀表达式字符串转成对应的数组
// s="1+((2+3)×4)-5";
public static List<String> toInfixExpressionList(String s) {
//定义一个List,存放中缀表达式 对应的内容
List<String> ls = new ArrayList<String>();
int i = 0; //这时是一个指针,用于遍历 中缀表达式字符串
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 置成"" '0'[48]->'9'[57]
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;//返回
}
//第二步:将中缀表达式对应的List转为后缀表达式对应的List
//即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =》 ArrayList [1,2,3,+,4,*,+,5,–]
public static List<String> parseSuffixExpreesionList(List<String> ls){
//定义两个栈:符号栈和存储中间结果的桟
Stack<String> s1 = new Stack<String>(); // 符号栈
//说明:因为s2 这个栈,在整个转换过程中,没有pop操作,而且后面我们还需要逆序输出
//因此比较麻烦,这里我们就不用 Stack<String> 直接使用 List<String> s2
//Stack<String> s2 = new Stack<String>(); // 储存中间结果的栈s2
List<String> s2 = new ArrayList<String>(); // 储存中间结果的Lists2
//遍历ls
for(String item: ls) {
if(item.matches("\\d+")) {//匹配多位数
s2.add(item);// //如果是一个数,直接加入s2
} else if (item.equals("(")) {
s1.push(item);//如果是(,直接加入s1
} else if (item.equals(")")) {
//如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
while(!s1.peek().equals("(")) {
s2.add(s1.pop());
}
s1.pop();//!!! 将 ( 弹出 s1栈, 消除小括号
} else {
//当item的优先级小于等于s1栈顶运算符, 将s1栈顶的运算符弹出并加入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
//注意:这里有一个比较优先级高低的方法Operation.getValue()
while(s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item) ) {
s2.add(s1.pop());
}
//还需要将item压入栈
s1.push(item);
}
}
//将s1中剩余的运算符依次弹出并加入s2
while(s1.size() != 0) {
s2.add(s1.pop());
}
return s2; //注意因为是存放到List, 因此按顺序输出就是对应的后缀表达式对应的List
}
//第三步:逆波兰表达式的求值
/**
1)从左至右扫描,将3和4压入堆栈;
2)遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
3)将5入栈;
4)接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
5)将6入栈;
6)最后是-运算符,计算出35-6的值,即29,由此得出最终结果
*/
public static int calculate(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);
}
}
//最后留在stack中的数据是运算结果
return Integer.parseInt(stack.pop());
}
//编写一个类 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;
default:
System.out.println("不存在该运算符" + operation);
break;
}
return result;
}
}
三 关注我
如果觉得我写的不错,请点个赞 关注我 您的支持是我更文最大的动力!