一、逆波兰(后缀)表达式
逆波兰式(Reverse Polish notation,RPN,或逆波兰记法),也叫后缀表达式(将运算符写在操作数之后)
定义
一个表达式E的后缀形式可以如下定义:
(1)如果E是一个变量或常量,则E的后缀式是E本身。
(2)如果E是E1 op E2形式的表达式,这里op是任何二元操作符,则E的后缀式为E1’E2’ op,这里E1’和E2’分别为E1和E2的后缀式。
(3)如果E是(E1)形式的表达式,则E1的后缀式就是E的后缀式。
如:我们平时写a+b,这是中缀表达式,写成后缀表达式就是:ab+
(a+b)c-(a+b)/e的后缀表达式为:
(a+b)c-(a+b)/e
→((a+b)c)((a+b)/e)-
→((a+b)c)((a+b)e/)-
→(ab+c)(ab+e/)-
→ab+cab+e/-
作用
实现逆波兰式的算法,难度并不大,但为什么要将看似简单的中缀表达式转换为复杂的逆波兰式?原因就在于这个简单是相对人类的思维结构来说的,对计算机而言中序表达式是非常复杂的结构。相对的,逆波兰式在计算机看来却是比较简单易懂的结构。因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。
二、中缀表达式→后缀表达式
1、思路分析
- 初始化两个栈:运算符栈s1,存储中间结果的栈s2
- 从左至右扫描中缀表达式
- 扫描到的是操作数,压入s2
- 扫描到的是运算符:
- 如果s1为空,或栈顶运算符为“)”,则直接将此运算符压入s1
- 如果s1不为空,则比较其与s1栈顶运算符的优先级:
- 若该运算符优先级比栈顶运算符的高,则直接将此运算符压入s1
- 若该运算符优先级比栈顶运算符的低,将s1栈顶的运算符弹出并压入到s2中,再重新回到4.1进行判断。
- 扫描到的是括号:
- 如果是左括号“(”,则直接压入s1
- 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃(pop出栈)
- 扫描直到表达式最右端
- 将s1中剩余的运算符依次弹出并压入s2
- 依次弹出s2中的元素并输出,结果的逆序即为所要的后缀表达式(因为栈是先入后出的,所以直接pop后其实是对应后缀表达式的一个逆序,下面的代码实现中我们使用list来代替这里的s2,避免了结果还需要逆序输出)
2、代码实现
1. 首先将中缀表达式中的各个字符按序存放到一个List中方便后面的遍历使用
/**
* 将中缀表达式中各个字符存储到List中利于后续的遍历使用
* @param s 中缀表达式
* @return 存储中缀表达式中各字符的List
*/
public static List<String> toInfixExpressionList(String s) {
List<String> list = new ArrayList<>();
int i = 0;//用于遍历的指针
String str;//多位数的拼接
char c;//每遍历到一个字符就放到c
do {
//如果c是一个非数字就加入到list
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) {
str += c;//拼接
i++;
}
list.add(str);
}
} while (i < s.length());
return list;
}
2. 定义一个类来进行运算符优先级的判断
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 res = 0;
switch (operation) {
case "+":
res = ADD;
break;
case "-":
res = SUB;
break;
case "*":
res = MUL;
break;
case "/":
res = DIV;
break;
}
return res;
}
3. 遍历List中存放的字符,按照分析思路进行转换
注意:由于存放中间结果的栈s2在转换过程中仅仅包含入栈push操作,所以我们这里用一个List来代替存放中间结果的栈s2,这样代替也可以达到目的,并且遍历输出list后就是对应的后缀表达式,无需再进行逆序输出。
/**
* 中缀表达转换为后缀表达式
* @param ls 存放中缀表达式各符号的list
* @return 存放后缀表达式的List
*/
public static List<String> parseSuffixExpression(List<String> ls) {
//定义两个栈
Stack<String> stack = new Stack<>();
List<String> list = new ArrayList<>();
//遍历ls
for (String str : ls) {
if (str.matches("\\d+")) {//正则表达式:匹配多位数
list.add(str);
} else if (str.equals("(")) {
stack.push(str);
} else if (str.equals(")")) {
while (!stack.peek().equals("(")) {
list.add(stack.pop());
}
stack.pop();//将(弹出,消除小括号
} else {
while (stack.size() != 0 && Operation.getValue(stack.peek()) >= Operation.getValue(str)) {//判断优先级
list.add(stack.pop());
}
stack.push(str);
}
}
while (stack.size() != 0) {
list.add(stack.pop());
}
return list;
}
4. 写一个Demo进行测试
import java.util.*;
/**
1. @author dankejun
2. @create 2020-04-19 13:41
*/
public class PolandNotation {
public static void main(String[] args) {
// String expression = "1+((2+3)*4)-5";
System.out.print("请输入一个中缀表达式:");
Scanner scanner = new Scanner(System.in);
String expression = scanner.next();
List<String> infixExpressionList = toInfixExpressionList(expression);//存放中缀表达式各字符的List
System.out.println("中缀表达式对应的List" + infixExpressionList);
List<String> parseSuffixExpression = parseSuffixExpression(infixExpressionList);//存放后缀表达式各字符的List
System.out.println("后缀表达式对应的List" + parseSuffixExpression);
//遍历输出对应的后缀表达式
String suffixExpression = "";
for (String s : parseSuffixExpression) {
suffixExpression += s;
}
System.out.println("输入的中缀表达式对应的后缀表达式为:" + suffixExpression);
}
}
测试结果:
三、简单逆波兰表达式计算器
1、思路分析
这里我们对逆波兰表达式进行一个简单的应用,完成一个简单的支持小括号和多位整数的逆波兰表达式计算器。
思路分析:
- 初始化一个栈s
- 从左至右扫描表达式
- 如果扫描到的是操作数,则直接入栈
- 如果扫描到的是运算符,就pop出栈顶元素num2与次顶元素num1,并用该运算符进行运算,最后将结果压入栈s中
- 直到栈中剩下一个元素,即为最后的计算结果
2、代码实现
1. 首先将逆波兰表达式放到List中,方便遍历使用,为了方便,逆波兰表达式的各字符用空格隔开
/**
* 将逆波兰表达式放到List中,方便遍历使用
* @param suffixExpression 逆波兰表达式
* @return 存储逆波兰表达式中各字符的List
*/
public static List<String> getListString(String suffixExpression) {
String[] split = suffixExpression.split(" ");
ArrayList<String> list = new ArrayList<>();
for (String str : split) {
list.add(str);
}
return list;
}
2. 实现基本的计算功能
/**
* 逆波兰计算器
* @param ls 存储逆波兰表达式中各字符的List
* @return 逆波兰计算器计算结果
*/
public static int calculate(List<String> ls) {
Stack<String> stack = new Stack<>();
//遍历
for (String str : ls) {
if (str.matches("\\d+")) {//正则表达式:匹配多位数
//入栈
stack.push(str);
} else {
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = 0;
if (str.equals("+")) {
res = num1 + num2;
} else if (str.equals("-")) {
res = num1 - num2;
} else if (str.equals("*")) {
res = num1 * num2;
} else if (str.equals("/")) {
res = num1 / num2;
} else {
throw new RuntimeException("运算符有误");
}
stack.push(res + "");
}
}
return Integer.parseInt(stack.pop());
}
3. 写一个Demo进行简单测试
import java.util.*;
/**
* @author dankejun
* @create 2020-04-19 13:41
*/
public class PolandNotation {
public static void main(String[] args) {
//先定义一个逆波兰表达式
//为了方便,逆波兰表达式的各字符用空格隔开
String suffixExpression = "3 4 + 5 * 6 -";
List<String> rpnList = getListString(suffixExpression);
System.out.println(rpnList);
int res = calculate(rpnList);
System.out.println(res);
}
测试结果:
这里只是对逆波兰计算器的一个简单的实现,也可以将中缀表达式转后缀表达式与后缀表达式计算器结合起来,完成一个支持括号和多位整数的中缀表达式计算器。