栈结构解决后缀表达式问题

概述

本文原载于我的博客,地址:https://blog.guoziyang.top/archives/26/

题目来源于数据结构课的第一次实验的第二题,题目如下:

表达式求值是实现程序设计语言的基本问题之一,也是栈的应用的一个典型例子。一个算术表达式是由操作数(operand)、运算符(operator)和界限符(delimiter)组成的。假设操作数是正整数,运算符只含加减乘除等四种运算符,界限符有左右括号和表达式起始、结束符“#”,如:#(7+15)*(23-28/4)#。引入表达式起始、结束符是为了方便。设计一个程序,演示用算符优先法对算术表达式求值的过程。
实验要求:
1.	从文本文件输入任意一个语法正确的(中缀)表达式,显示并保存该表达式。
2.	利用栈结构,把上述(中缀)表达式转换成后缀表达式,并显示栈的状态变化过程和所得到的后缀表达式。
3.	利用栈结构,对上述后缀表达式进行求值,并显示栈的状态变化过程和最终结果。

计算机对于后缀表达式的计算远远快于对于中缀表达式,所以我们大概是需要模拟计算机对于中缀表达式的计算过程。该题的实验语言依旧为Java。

该题可以简化成三个步骤:

  1. 读入中缀表达式
  2. 转化为后缀表达式
  3. 计算后缀表达式

读入表达式

读入文件中的中缀表达式,并且将前后的#符号删除之后存入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,便于后续的遍历处理。

中缀表达式转化为后缀表达式

接着将中缀表达式转化为后缀表达式。转化的算法如下:

  1. 初始化一个栈

  2. 逐个读取元素(数字或者操作符)

  3. 如果遇到数字,直接输出

  4. 如果遇到操作符(不考虑括号),如果其优先级大于栈顶元素,就将栈顶弹出,并重复4步骤,否则将该操作符压入栈中(栈为空的时候也直接压栈即可)

  5. 如果遇到左括号("("),直接将其压入栈中,如果遇到右括号(")"),循环弹出顶栈元素,直到左括号为止(左括号也需要弹出,右括号不需要压栈),并且输出所有被弹栈顶元素(左括号除外)

最开始我的实现方法是逐个字符读取,几乎写完这个方法才发现无法处理除了一位数以外的情况,于是只好重构……

实现如下:

首先我们先将待处理的中缀表达式字符串分割为一个个的元素(包括每一个完整的数值和运算符):

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用于存放各个运算符的优先级,其实括号的操作符本来是最高的,但是如果将其定义为最高,会导致后续操作符无法入栈,所以暂时将左括号的优先级定为最低。剩下的就是简单的操作符判断了。

为了便于后续操作,我将后缀表达式的每个元素之间都使用空格隔开。

后缀表达式的计算

对于一个现有的后缀表达式的计算,有一个现有的算法:

  1. 初始化一个栈
  2. 遍历后缀表达式的所有元素
  3. 如果遇到数字,就将其直接压入栈
  4. 如果遇到运算符,就将顶栈的两个元素弹出,将执行该运算之后结果在压入栈内
  5. 遍历结束后,栈内的唯一一个元素就是计算结果。

有一个细节:对于一个有顺序的运算,如减法或除法,应当是后弹出的节点是被减数(被除数),而先弹出的是减数(除数)

首先实现这个栈:

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();
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值