[Code] 中缀式转后缀式

概要

对于一个可带括号的中缀四则运算表达式, 例如30 + 4 / 230 / ( 4 + 2 ), 下面代码将分别转换为对应的后缀表达形式 30 4 2 / +30 4 2 + /. 要求每个 token 之间以若干个空白符隔开, 输入的中缀式为单行.

代码

import java.util.Scanner;
import java.util.Deque;
import java.util.ArrayDeque;
import java.util.Map;

public class Convertor {
  public static void main(String[] main) {
    Scanner in = new Scanner(System.in);
    String expression = in.nextLine();
    expression = "( " + expression + " )";
    String[] tokens = expression.split("\\s+");

    Deque<String> opStack = new ArrayDeque<>();
    Deque<String> nuStack = new ArrayDeque<>();

    Map<String, Integer> opPriority =
      Map.of("+", 0, "-", 0, "*", 1, "/", 1, "(", -1);

    for (String token: tokens) {
      if (token.equals("(")) {
        opStack.addFirst(token);
      } else if (token.equals(")")) {
        while (!opStack.peekFirst().equals("(")) {
          produceOne(opStack, nuStack);
        }
        opStack.removeFirst();
      } else if (opPriority.containsKey(token)) {
        while ((!opStack.isEmpty()) && (opPriority.get(token) <=  opPriority.get(opStack.peekFirst()))) {
          produceOne(opStack, nuStack);
        }
        opStack.addFirst(token);
      } else { // number
        nuStack.addFirst(token);
      }
    }
    System.out.println(nuStack.removeFirst().replaceAll("\\s+", " ").trim());
  }

  private static void produceOne(Deque<String> opStack, Deque<String> nuStack) {
    String production = filter(nuStack.removeFirst()) + " ";
    production = filter(nuStack.removeFirst()) + " " + production + " ";
    production = production + " " + opStack.removeFirst() + " ";
    nuStack.addFirst(production);
  }

  private static String filter(String token) {
    return token.equals("@") ? "" : token;
  }
}

分析

  1. 为了统一处理(避免分类讨论), 对于输入的中缀表达式直接加上两端的括号. 这样的操作并不会影响整个求解的结果, 而不加这个括号需要额外处理最终操作符栈剩余的操作符.
  2. 逐个 token 处理,
    1. 对于操作数, 直接入栈.
    2. 左右括号配对处理.
    3. 对于一般的操作符, 在此处充当 trigger 的作用. 逻辑上, 第一个操作符入栈, 对于后续的操作符, 先逐个弹出操作符栈中优先级不小于当前操作符的运算符以及相应的操作数, 在将当前的操作符入栈. 弹出优先级大的操作符是显然的, 弹出优先级相等的操作符是由于要满足左结合性, 若是右结合操作符, 则不弹出. 注意弹出操作符并产生代表运算结果的字符串后仍需将其当做结果压回操作数栈中.
  3. 细节上,
    1. 使用 Java 9 的 Map.of 函数简化 opPriority 字典的构建代码.
    2. 使用效率更高实现更完备的 Deque 并将操作限制在栈操作上以代替不再推荐的 Stack.
    3. 为了简化讨论, 将 ( 的优先级定义为最低.
    4. 为了简化讨论, 先在各结果 token 之间插入若干个空白符, 输出时再统一压缩多余的空白符.
  4. 假定输入都是合法的, 没有对不满足概要中假设的前提的非法输入进行处理.
05-07 15:45