概述
本文原载于我的博客,地址:https://blog.guoziyang.top/archives/26/
题目来源于数据结构课的第一次实验的第二题,题目如下:
表达式求值是实现程序设计语言的基本问题之一,也是栈的应用的一个典型例子。一个算术表达式是由操作数(operand)、运算符(operator)和界限符(delimiter)组成的。假设操作数是正整数,运算符只含加减乘除等四种运算符,界限符有左右括号和表达式起始、结束符“#”,如:#(7+15)*(23-28/4)#。引入表达式起始、结束符是为了方便。设计一个程序,演示用算符优先法对算术表达式求值的过程。
实验要求:
1. 从文本文件输入任意一个语法正确的(中缀)表达式,显示并保存该表达式。
2. 利用栈结构,把上述(中缀)表达式转换成后缀表达式,并显示栈的状态变化过程和所得到的后缀表达式。
3. 利用栈结构,对上述后缀表达式进行求值,并显示栈的状态变化过程和最终结果。
计算机对于后缀表达式的计算远远快于对于中缀表达式,所以我们大概是需要模拟计算机对于中缀表达式的计算过程。该题的实验语言依旧为Java。
该题可以简化成三个步骤:
- 读入中缀表达式
- 转化为后缀表达式
- 计算后缀表达式
读入表达式
读入文件中的中缀表达式,并且将前后的#符号删除之后存入List:
private static void readFromFile(String path) {
try{
BufferedReader bufferedReader = new BufferedReader(new FileReader(new File(path)));
String tempStr = "";
while((tempStr = bufferedReader.readLine()) != null) {
tempStr = tempStr.substring(1, tempStr.length() - 1);
infixExpressions.add(tempStr);
}
bufferedReader.close();
}catch(Exception e) {
e.printStackTrace();
}
}
将所有的中缀表达式存入List,便于后续的遍历处理。
中缀表达式转化为后缀表达式
接着将中缀表达式转化为后缀表达式。转化的算法如下:
-
初始化一个栈
-
逐个读取元素(数字或者操作符)
-
如果遇到数字,直接输出
-
如果遇到操作符(不考虑括号),如果其优先级大于栈顶元素,就将栈顶弹出,并重复4步骤,否则将该操作符压入栈中(栈为空的时候也直接压栈即可)
-
如果遇到左括号("("),直接将其压入栈中,如果遇到右括号(")"),循环弹出顶栈元素,直到左括号为止(左括号也需要弹出,右括号不需要压栈),并且输出所有被弹栈顶元素(左括号除外)
最开始我的实现方法是逐个字符读取,几乎写完这个方法才发现无法处理除了一位数以外的情况,于是只好重构……
实现如下:
首先我们先将待处理的中缀表达式字符串分割为一个个的元素(包括每一个完整的数值和运算符):
private static ArrayList<String> splitIntoPieces(String infixExpression) {
ArrayList<String> elementList = new ArrayList<>();
for(int i = 0; i < infixExpression.length(); i ++) {
for(int j = i; j < infixExpression.length(); j ++) {
if(infixExpression.charAt(j) == '+' || infixExpression.charAt(j) == '-' ||
infixExpression.charAt(j) == '*' || infixExpression.charAt(j) == '/' ||
infixExpression.charAt(j) == '(' || infixExpression.charAt(j) == ')') {
if(infixExpression.charAt(i) == '+' || infixExpression.charAt(i) == '-' ||
infixExpression.charAt(i) == '*' || infixExpression.charAt(i) == '/' ||
infixExpression.charAt(i) == '(' || infixExpression.charAt(i) == ')') {
elementList.add(infixExpression.substring(i, j + 1));
i = j;
break;
} else {
elementList.add(infixExpression.substring(i, j));
i = j - 1;
break;
}
}else if(j == infixExpression.length() - 1) {
elementList.add(infixExpression.substring(i, j + 1));
}
}
}
return elementList;
}
这个方法将中缀表达式的元素(数值和运算符)分割后存入List里面并且返回,算法就是最朴素的分割字符串法,非常简单。
之后对这些元素进行上面的算法操作即可。
首先我们需要定义这个栈结构:
class ConvertStack {
private ArrayList<String> strList = new ArrayList<>();
public void push(String tempStr) {
strList.add(tempStr);
}
public String pop() {
String tempStr = strList.get(strList.size() - 1);
strList.remove(strList.size() - 1);
return tempStr;
}
public String getTop() {
return strList.get(strList.size() - 1);
}
public int getSize() {
return strList.size();
}
public boolean isEmpty() {
if(strList.isEmpty()) {
return true;
} else {
return false;
}
}
@Override
public String toString() {
return strList.toString();
}
}
重写了一下toString()方法,便于debug……
接着就可以一波操作了,完整的将中缀表达式转化为后缀表达式的方法如下:
private static String convertToPostfix(String infixExpression) {
StringBuilder postfixExpression = new StringBuilder();
HashMap<String, Integer> priorityOrder = new HashMap<>();
ConvertStack convertStack = new ConvertStack();
priorityOrder.put("*", 2);
priorityOrder.put("/", 2);
priorityOrder.put("+", 1);
priorityOrder.put("-", 1);
priorityOrder.put("(", 0);
ArrayList<String> elementList = splitIntoPieces(infixExpression);
for(int i = 0; i < elementList.size(); i ++) {
String element = elementList.get(i);
if("+".equals(element) || "-".equals(element) || "*".equals(element) || "/".equals(element) || "(".equals(element) || ")".equals(element)) {
if(")".equals(element)) {
String tempString = null;
while(!"(".equals(tempString = convertStack.pop())) {
postfixExpression.append(tempString + " ");
}
}else if("(".equals(element)){
convertStack.push(element);
} else{
if(convertStack.isEmpty() || priorityOrder.get(element) > priorityOrder.get(convertStack.getTop())) {
convertStack.push(element);
} else {
postfixExpression.append(convertStack.pop() + " ");
i --;
continue;
}
}
}else {
postfixExpression.append(element + " ");
}
}
while(!convertStack.isEmpty()) {
postfixExpression.append(convertStack.pop() + " ");
}
return postfixExpression.toString().trim();
}
定义了一个HashMap用于存放各个运算符的优先级,其实括号的操作符本来是最高的,但是如果将其定义为最高,会导致后续操作符无法入栈,所以暂时将左括号的优先级定为最低。剩下的就是简单的操作符判断了。
为了便于后续操作,我将后缀表达式的每个元素之间都使用空格隔开。
后缀表达式的计算
对于一个现有的后缀表达式的计算,有一个现有的算法:
- 初始化一个栈
- 遍历后缀表达式的所有元素
- 如果遇到数字,就将其直接压入栈
- 如果遇到运算符,就将顶栈的两个元素弹出,将执行该运算之后结果在压入栈内
- 遍历结束后,栈内的唯一一个元素就是计算结果。
有一个细节:对于一个有顺序的运算,如减法或除法,应当是后弹出的节点是被减数(被除数),而先弹出的是减数(除数)
首先实现这个栈:
class CalculateStack {
ArrayList<Integer> numberList = new ArrayList<>();
public void push(int number) {
numberList.add(number);
}
private int pop() {
int tempNumber = numberList.get(numberList.size() - 1);
numberList.remove(numberList.size() - 1);
return tempNumber;
}
public ArrayList<Integer> popTwo() {
ArrayList<Integer> tempList = new ArrayList<>();
tempList.add(this.pop());
tempList.add(this.pop());
return tempList;
}
public int getTop() {
return numberList.get(numberList.size() - 1);
}
}
其中pop()方法不对外开放,而开放popTwo()方法,因为一般需要的是弹出两个数字,而不是一个。
接着实现这个算法:
private static int calculatePostfixExpression(String postfixExpression) {
String[] elementArray = postfixExpression.split(" ");
CalculateStack calculateStack = new CalculateStack();
int tempAns = 0;
for(int i = 0; i < elementArray.length; i ++) {
String element = elementArray[i];
if("+".equals(element) || "-".equals(element) ||
"*".equals(element) || "/".equals(element)) {
ArrayList<Integer> tempList = calculateStack.popTwo();
if("+".equals(element)) {
tempAns = tempList.get(1) + tempList.get(0);
calculateStack.push(tempAns);
}else if("-".equals(element)) {
tempAns = tempList.get(1) - tempList.get(0);
calculateStack.push(tempAns);
}else if("*".equals(element)) {
tempAns = tempList.get(1) * tempList.get(0);
calculateStack.push(tempAns);
}else if("/".equals(element)) {
tempAns = tempList.get(1) / tempList.get(0);
calculateStack.push(tempAns);
}
} else {
calculateStack.push(Integer.valueOf(element));
}
}
return calculateStack.getTop();
}
这个算法十分容易,一目了然。最后返回的数值就是计算结果。
完整代码实现
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
public class DSExperiment {
private static ArrayList<String> infixExpressions = new ArrayList<>();
public static void main(String[] args) {
readFromFile("samples.txt");
calculateExpressions();
}
private static void readFromFile(String path) {
try{
BufferedReader bufferedReader = new BufferedReader(new FileReader(new File(path)));
String tempStr = "";
while((tempStr = bufferedReader.readLine()) != null) {
tempStr = tempStr.substring(1, tempStr.length() - 1);
infixExpressions.add(tempStr);
}
bufferedReader.close();
}catch(Exception e) {
e.printStackTrace();
}
}
private static void calculateExpressions() {
for(String infixExpression : infixExpressions) {
System.out.println("The infix expression is " + infixExpression);
String postfixExpression = convertToPostfix(infixExpression);
System.out.println("The postfix expression is " + postfixExpression);
int ans = calculatePostfixExpression(postfixExpression);
System.out.println("The answer is " + ans + "\n");
}
}
private static String convertToPostfix(String infixExpression) {
StringBuilder postfixExpression = new StringBuilder();
HashMap<String, Integer> priorityOrder = new HashMap<>();
ConvertStack convertStack = new ConvertStack();
priorityOrder.put("*", 2);
priorityOrder.put("/", 2);
priorityOrder.put("+", 1);
priorityOrder.put("-", 1);
priorityOrder.put("(", 0);
ArrayList<String> elementList = splitIntoPieces(infixExpression);
for(int i = 0; i < elementList.size(); i ++) {
String element = elementList.get(i);
if("+".equals(element) || "-".equals(element) || "*".equals(element) || "/".equals(element) || "(".equals(element) || ")".equals(element)) {
if(")".equals(element)) {
String tempString = null;
while(!"(".equals(tempString = convertStack.pop())) {
postfixExpression.append(tempString + " ");
}
}else if("(".equals(element)){
convertStack.push(element);
} else{
if(convertStack.isEmpty() || priorityOrder.get(element) > priorityOrder.get(convertStack.getTop())) {
convertStack.push(element);
} else {
postfixExpression.append(convertStack.pop() + " ");
i --;
continue;
}
}
}else {
postfixExpression.append(element + " ");
}
}
while(!convertStack.isEmpty()) {
postfixExpression.append(convertStack.pop() + " ");
}
return postfixExpression.toString().trim();
}
private static ArrayList<String> splitIntoPieces(String infixExpression) {
ArrayList<String> elementList = new ArrayList<>();
for(int i = 0; i < infixExpression.length(); i ++) {
for(int j = i; j < infixExpression.length(); j ++) {
if(infixExpression.charAt(j) == '+' || infixExpression.charAt(j) == '-' ||
infixExpression.charAt(j) == '*' || infixExpression.charAt(j) == '/' ||
infixExpression.charAt(j) == '(' || infixExpression.charAt(j) == ')') {
if(infixExpression.charAt(i) == '+' || infixExpression.charAt(i) == '-' ||
infixExpression.charAt(i) == '*' || infixExpression.charAt(i) == '/' ||
infixExpression.charAt(i) == '(' || infixExpression.charAt(i) == ')') {
elementList.add(infixExpression.substring(i, j + 1));
i = j;
break;
} else {
elementList.add(infixExpression.substring(i, j));
i = j - 1;
break;
}
}else if(j == infixExpression.length() - 1) {
elementList.add(infixExpression.substring(i, j + 1));
}
}
}
return elementList;
}
private static int calculatePostfixExpression(String postfixExpression) {
String[] elementArray = postfixExpression.split(" ");
CalculateStack calculateStack = new CalculateStack();
int tempAns = 0;
for(int i = 0; i < elementArray.length; i ++) {
String element = elementArray[i];
if("+".equals(element) || "-".equals(element) ||
"*".equals(element) || "/".equals(element)) {
ArrayList<Integer> tempList = calculateStack.popTwo();
if("+".equals(element)) {
tempAns = tempList.get(1) + tempList.get(0);
calculateStack.push(tempAns);
}else if("-".equals(element)) {
tempAns = tempList.get(1) - tempList.get(0);
calculateStack.push(tempAns);
}else if("*".equals(element)) {
tempAns = tempList.get(1) * tempList.get(0);
calculateStack.push(tempAns);
}else if("/".equals(element)) {
tempAns = tempList.get(1) / tempList.get(0);
calculateStack.push(tempAns);
}
} else {
calculateStack.push(Integer.valueOf(element));
}
}
return calculateStack.getTop();
}
}
class ConvertStack {
private ArrayList<String> strList = new ArrayList<>();
public void push(String tempStr) {
strList.add(tempStr);
}
public String pop() {
String tempStr = strList.get(strList.size() - 1);
strList.remove(strList.size() - 1);
return tempStr;
}
public String getTop() {
return strList.get(strList.size() - 1);
}
public int getSize() {
return strList.size();
}
public boolean isEmpty() {
if(strList.isEmpty()) {
return true;
} else {
return false;
}
}
@Override
public String toString() {
return strList.toString();
}
}
class CalculateStack {
ArrayList<Integer> numberList = new ArrayList<>();
public void push(int number) {
numberList.add(number);
}
private int pop() {
int tempNumber = numberList.get(numberList.size() - 1);
numberList.remove(numberList.size() - 1);
return tempNumber;
}
public ArrayList<Integer> popTwo() {
ArrayList<Integer> tempList = new ArrayList<>();
tempList.add(this.pop());
tempList.add(this.pop());
return tempList;
}
public int getTop() {
return numberList.get(numberList.size() - 1);
}
}
运行结果:
GuodeMacBook-Air:DSExperiment guoziyang$ java DSExperiment
The infix expression is (7+15)*(23-28/4)
The postfix expression is 7 15 + 23 28 4 / - *
The answer is 352
The infix expression is 1+((2+3)*4)-5
The postfix expression is 1 2 3 + 4 * + 5 -
The answer is 16
最后实现的时候,把函数扩充到了实数域,添加了乘方运算符。添加了不完整的变量运算。
变量运算本来想讲一个变量式化到最简的,但由于多变量要考虑分配律和合并同类项问题,过于复杂,只好作罢,仅仅保留了一个把中缀变量式化为后缀变量式的功能。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
public class DSExperiment {
private static ArrayList<String> infixExpressions = new ArrayList<>();
public static void main(String[] args) {
readFromFile("samples.txt");
calculateExpressions();
}
private static void readFromFile(String path) {
try{
BufferedReader bufferedReader = new BufferedReader(new FileReader(new File(path)));
String tempStr = "";
while((tempStr = bufferedReader.readLine()) != null) {
tempStr = tempStr.substring(1, tempStr.length() - 1);
infixExpressions.add(tempStr);
}
bufferedReader.close();
}catch(Exception e) {
e.printStackTrace();
}
}
private static void calculateExpressions() {
for(String infixExpression : infixExpressions) {
System.out.println("The infix expression is " + infixExpression);
if(infixExpression.matches(".*[a-zA-Z].*")) {
String postfixExpression = convertToPostfix(addMultiply(infixExpression));
System.out.println("The postfix expression is " + postfixExpression);
System.out.println();
//String ans = simplifyExpression(postfixExpression);
//System.out.println("The result is " + ans + "\n");
} else {
String postfixExpression = convertToPostfix(infixExpression);
System.out.println("The postfix expression is " + postfixExpression);
double ans = calculatePostfixExpression(postfixExpression);
System.out.printf("The result is %.2f\n\n", ans);
}
}
}
private static String addMultiply(String infixExpression) {
int j = 0;
int length = infixExpression.length();
for(int i = 0; i < length - 1; i ++) {
if((infixExpression.charAt(j) >= 48 && infixExpression.charAt(j) <= 57) &&
((infixExpression.charAt(j + 1) >= 97 && infixExpression.charAt(j + 1) <= 122)
|| (infixExpression.charAt(j + 1) >= 65 && infixExpression.charAt(j + 1) <= 90))) {
StringBuilder tempBuilder = new StringBuilder(infixExpression);
tempBuilder.insert(j + 1, "*");
infixExpression = tempBuilder.toString();
j ++;
}
j ++;
}
return infixExpression;
}
private static String convertToPostfix(String infixExpression) {
StringBuilder postfixExpression = new StringBuilder();
HashMap<String, Integer> priorityOrder = new HashMap<>();
Stack<String> convertStack = new Stack<>();
priorityOrder.put("^", 3);
priorityOrder.put("*", 2);
priorityOrder.put("/", 2);
priorityOrder.put("+", 1);
priorityOrder.put("-", 1);
priorityOrder.put("(", 0);
ArrayList<String> elementList = splitIntoPieces(infixExpression);
for(int i = 0; i < elementList.size(); i ++) {
String element = elementList.get(i);
if("+".equals(element) || "-".equals(element) ||
"*".equals(element) || "/".equals(element) ||
"(".equals(element) || ")".equals(element) ||
"^".equals(element)) {
if(")".equals(element)) {
String tempString = null;
while(!"(".equals(tempString = convertStack.pop())) {
postfixExpression.append(tempString + " ");
}
}else if("(".equals(element)){
convertStack.push(element);
} else{
if(convertStack.isEmpty() || priorityOrder.get(element) > priorityOrder.get(convertStack.getTop())) {
convertStack.push(element);
} else {
postfixExpression.append(convertStack.pop() + " ");
i --;
continue;
}
}
}else {
postfixExpression.append(element + " ");
}
}
while(!convertStack.isEmpty()) {
postfixExpression.append(convertStack.pop() + " ");
}
return postfixExpression.toString().trim();
}
private static ArrayList<String> splitIntoPieces(String infixExpression) {
ArrayList<String> elementList = new ArrayList<>();
outer:for(int i = 0; i < infixExpression.length(); i ++) {
for(int j = i; j < infixExpression.length(); j ++) {
if(infixExpression.charAt(j) == '+' || infixExpression.charAt(j) == '-' ||
infixExpression.charAt(j) == '*' || infixExpression.charAt(j) == '/' ||
infixExpression.charAt(j) == '(' || infixExpression.charAt(j) == ')' ||
infixExpression.charAt(j) == '^') {
if(infixExpression.charAt(i) == '+' || infixExpression.charAt(i) == '-' ||
infixExpression.charAt(i) == '*' || infixExpression.charAt(i) == '/' ||
infixExpression.charAt(i) == '(' || infixExpression.charAt(i) == ')' ||
infixExpression.charAt(i) == '^') {
elementList.add(infixExpression.substring(i, j + 1));
i = j;
break;
} else {
elementList.add(infixExpression.substring(i, j));
i = j - 1;
break;
}
}else if(j == infixExpression.length() - 1) {
elementList.add(infixExpression.substring(i, j + 1));
break outer;
}
}
}
return elementList;
}
private static double calculatePostfixExpression(String postfixExpression) {
String[] elementArray = postfixExpression.split(" ");
Stack<Double> calculateStack = new Stack<>();
Double tempAns = 0.0;
for(int i = 0; i < elementArray.length; i ++) {
String element = elementArray[i];
if("+".equals(element) || "-".equals(element) ||
"*".equals(element) || "/".equals(element) ||
"^".equals(element) ) {
ArrayList<Double> tempList = calculateStack.popTwo();
if("+".equals(element)) {
tempAns = tempList.get(1) + tempList.get(0);
calculateStack.push(tempAns);
}else if("-".equals(element)) {
tempAns = tempList.get(1) - tempList.get(0);
calculateStack.push(tempAns);
}else if("*".equals(element)) {
tempAns = tempList.get(1) * tempList.get(0);
calculateStack.push(tempAns);
}else if("/".equals(element)) {
tempAns = tempList.get(1) / tempList.get(0);
calculateStack.push(tempAns);
}else if("^".equals(element)) {
tempAns = Math.pow(tempList.get(1), tempList.get(0));
calculateStack.push(tempAns);
}
} else {
calculateStack.push(Double.valueOf(element));
}
}
return calculateStack.getTop();
}
}
class Stack<T> {
private ArrayList<T> list = new ArrayList<>();
public void push(T element) {
list.add(element);
}
public T pop() {
T element = list.get(list.size() - 1);
list.remove(list.size() - 1);
return element;
}
public ArrayList<T> popTwo() {
ArrayList<T> elements = new ArrayList<>();
elements.add(this.pop());
elements.add(this.pop());
return elements;
}
public T getTop() {
return list.get(list.size() - 1);
}
public int getSize() {
return list.size();
}
public boolean isEmpty() {
if(list.isEmpty()) {
return true;
} else {
return false;
}
}
@Override
public String toString() {
return list.toString();
}
}