逆波兰计算器

短命女 2022-12-02 10:52 185阅读 0赞

逆波兰表达式又叫做后缀表达式,逆波兰表达式把运算量写在前面,把运算符写在后面。

我们平时书写的表达式都是中缀表达式,这个表达式更加方便我们进行计算。但是相对于计算机而言,逆波兰表达式运算更加方便。只需要入栈和出栈两种操作就可以搞定所有运算,遇到操作数就入栈,遇到运算符就出栈,然后将栈顶的两个操作数进行相应运算。

所以,要想用代码实现计算器的首要步骤,就是将 中缀表达式 转为 后缀表达式

中缀表达式转后缀表达式

步骤如下:

  1. 初始化两个栈:运算符栈 s1 和储存中间结果的栈 s2;
  2. 从左至右扫描中缀表达式;
  3. 遇到操作数时,将其压 s2;
  4. 遇到运算符时,比较其与 s1 栈顶运算符的优先级:

    ①. 如果 s1 为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;

    ②. 否则,若优先级比栈顶运算符的高,也将运算符压入 s1;

    ③. 否则,将 s1 栈顶的运算符弹出并压入到 s2 中,再次转到4.①与 s1 中新的栈顶运算符相比较;

  5. 遇到括号时:

    ①. 如果是左括号”(“,则直接压入 s1;

    ②. 如果是右括号”)”,则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃;

  6. 重复步骤 2—5,直到扫描到表达式的最右边。
  7. 将 s1 中剩余的运算符依次弹出并压入 s2。
  8. 依次弹出 s2 中的元素并输出,结果的逆序即为 中缀表达式 对应的 后缀表达式。

形如:

中缀表达式“1+((2+3)×4)-5”,转为后缀表达式的结果是:

1 2 3 + 4 × + 5 –

代码实现

下面的代码只能进行整数的运算。

首先声明一个常量类,里面记录着运算符的字符。

  1. public class Constants {
  2. public static final String ADD_SYMBOL = "+";
  3. public static final String SUB_SYMBOL = "-";
  4. public static final String MUL_SYMBOL = "*";
  5. public static final String DIV_SYMBOL = "/";
  6. }

接着创建一个返回运算符优先级的类。

  1. public class Operation {
  2. /** * 加 */
  3. private static int ADD = 1;
  4. /** * 减 */
  5. private static int SUB = 1;
  6. /** * 乘 */
  7. private static int MUL = 2;
  8. /** * 除 */
  9. private static int DIV = 2;
  10. /** * TODO 返回对应的优先级 */
  11. public static int getValue(String operand) {
  12. int result = 0;
  13. switch (operand) {
  14. case Constants.ADD_SYMBOL:
  15. result = ADD;
  16. break;
  17. case Constants.SUB_SYMBOL:
  18. result = SUB;
  19. break;
  20. case Constants.MUL_SYMBOL:
  21. result = MUL;
  22. break;
  23. case Constants.DIV_SYMBOL:
  24. result = DIV;
  25. break;
  26. default:
  27. System.out.println("不存在该运算符");
  28. break;
  29. }
  30. return result;
  31. }
  32. }

最后就是核心功能了。编写中缀表达式转后缀表达式、计算。

  1. public class ReversePolandCalculator {
  2. /** * TODO 将中缀表达式转 list 集合 * * @param str * @return java.util.List<java.lang.String> */
  3. public static List<String> infixExpressionToList(String str) {
  4. List<String> list = new ArrayList<>();
  5. // 作为一个指针
  6. int index = 0;
  7. int zeroAscii = 48;
  8. int nineAscii = 57;
  9. // 如果这个操作数是多位数
  10. StringBuilder mStr;
  11. // 存放每一个字符
  12. char ch;
  13. do {
  14. // 如果 ch 是一个 非数字,需要加入到 list(0-9的ASCII值是48-57)
  15. if ((ch = str.charAt(index)) < zeroAscii || (ch = str.charAt(index)) > nineAscii) {
  16. list.add("" + ch);
  17. // 指针后移
  18. index++;
  19. } else {
  20. // 如果是一个数,需要考虑多位数
  21. mStr = new StringBuilder();
  22. while (index < str.length() && (ch = str.charAt(index)) >= zeroAscii && (ch = str.charAt(index)) <= nineAscii) {
  23. mStr.append(ch);
  24. index++;
  25. }
  26. list.add(mStr.toString());
  27. }
  28. } while (index < str.length());
  29. return list;
  30. }
  31. /** * TODO 将 中缀表达式的 List 集合转 后缀表达式 * * @param infixList * @return java.util.List<java.lang.String> */
  32. public static List<String> infixToSuffixExpressionList(List<String> infixList) {
  33. // 符号栈
  34. Stack<String> stack = new Stack<>();
  35. // 存储中间结果的
  36. List<String> resultList = new ArrayList<>();
  37. String leftBracket = "(";
  38. String rightBracket = ")";
  39. // 遍历
  40. for (String item : infixList) {
  41. // 如果是一个数,加入 resultList
  42. if (item.matches("\\d+")) {
  43. resultList.add(item);
  44. } else if (item.equals(leftBracket)) {
  45. stack.push(item);
  46. } else if (item.equals(rightBracket)) {
  47. // 如果是 右括号,则依次弹出 stack 栈顶的运算符,并压入 resultList,直到遇到左括号为止,此时将这一对括号丢弃
  48. while (!stack.peek().equals(leftBracket)) {
  49. resultList.add(stack.pop());
  50. }
  51. // 将 ( 弹出 stack 栈,消除小括号
  52. stack.pop();
  53. } else {
  54. // 当 item 的优先级小于等于 stack栈顶运算符,将 stack 栈顶的运算符弹出并加入到 resultList 中,再次与 stack 中新的栈顶运算符相比较
  55. while (stack.size() != 0 && Operation.getValue(stack.peek()) >= Operation.getValue(item)) {
  56. resultList.add(stack.pop());
  57. }
  58. // 将 此时 的 item压入栈
  59. stack.push(item);
  60. }
  61. }
  62. // 将 stack 中剩余的运算符依次弹出并加入到 resultList
  63. while (stack.size() != 0) {
  64. resultList.add(stack.pop());
  65. }
  66. // 因为 存放到 List中,因此按顺序输出就是 对应的 后缀表达式
  67. return resultList;
  68. }
  69. /** * TODO 计算 * * @param expression * @return int */
  70. public static int calculate(List<String> expressionList) {
  71. // 创建栈
  72. Stack<String> stack = new Stack<>();
  73. // 遍历
  74. for (String item : expressionList) {
  75. // 使用正则取出数(匹配多位数)
  76. if (item.matches("\\d+")) {
  77. // 入栈
  78. stack.push(item);
  79. } else {
  80. // pop 出两个数,并运算,再入栈
  81. int num2 = Integer.parseInt(stack.pop());
  82. int num1 = Integer.parseInt(stack.pop());
  83. int res = 0;
  84. switch (item) {
  85. case Constants.ADD_SYMBOL:
  86. res = num1 + num2;
  87. break;
  88. case Constants.SUB_SYMBOL:
  89. res = num1 - num2;
  90. break;
  91. case Constants.MUL_SYMBOL:
  92. res = num1 * num2;
  93. break;
  94. case Constants.DIV_SYMBOL:
  95. res = num1 / num2;
  96. break;
  97. default:
  98. throw new RuntimeException("运算符有误");
  99. }
  100. // 把 res 入栈
  101. stack.push("" + res);
  102. }
  103. }
  104. // 最后留在 stack 中的数据是运算结果
  105. return Integer.parseInt(stack.pop());
  106. }
  107. }

编写测试方法。

  1. public class Test {
  2. public static void main(String[] args) {
  3. String expression = "1+((2+3)*4)-5";
  4. List<String> infixExpressionToList = ReversePolandCalculator.infixExpressionToList(expression);
  5. System.out.println("中缀表达式对应的 List=" + infixExpressionToList);
  6. List<String> suffixExpressionList = ReversePolandCalculator.infixToSuffixExpressionList(infixExpressionToList);
  7. System.out.println("后缀表达式对应的 List=" + suffixExpressionList);
  8. // 计算表达式的值
  9. System.out.printf("expression=%d", ReversePolandCalculator.calculate(suffixExpressionList));
  10. }
  11. }

输出结果:

  1. 中缀表达式对应的 List=[1, +, (, (, 2, +, 3, ), *, 4, ), -, 5]
  2. 不存在该运算符
  3. 不存在该运算符
  4. 后缀表达式对应的 List=[1, 2, 3, +, 4, *, +, 5, -]
  5. expression=16

基本上一个基于栈的逆波兰计算器就实现了。下面给出完整的逆波兰计算器代码,不仅支持多位数,还支持小数。还可以处理任何空白字符串,包括空格、制表符、换页符。

逆波兰计算器完成版

创建一个常量类

  1. public class Constants {
  2. public static final String LEFT = "(";
  3. public static final String RIGHT = ")";
  4. public static final String ADD = "+";
  5. public static final String MINUS = "-";
  6. public static final String TIMES = "*";
  7. public static final String DIVISION = "/";
  8. /** * 运算符级别: + - */
  9. public static final int LEVEL_1 = 1;
  10. /** * 运算符级别:* / */
  11. public static final int LEVEL_2 = 2;
  12. /** * int 的 最大值(代表括号的等级) */
  13. public static final int LEVEL_HIGH = Integer.MAX_VALUE;
  14. }

提取公共方法

  1. public class CommonUtils {
  2. /** * TODO 匹配运算符 */
  3. public static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)";
  4. private static final Pattern PATTERN = Pattern.compile("^[-\\+]?[.\\d]*$");
  5. /** * TODO 去除所有 空白字符 * * @param str * @return java.lang.String */
  6. public static String replaceAllBlank(String str) {
  7. // \\s+ 匹配任何空白字符,包括空格、制表符、换页符等等, 等价于[ \f\n\r\t\v]
  8. return str.replaceAll("\\s+", "");
  9. }
  10. /** * TODO 判断是不是数字 * * @param str * @return boolean */
  11. public static boolean isNumber(String str) {
  12. return PATTERN.matcher(str).matches();
  13. }
  14. /** * TODO 判断 是不是 运算符 * * @param str * @return boolean */
  15. public static boolean isSymbol(String str) {
  16. return str.matches(SYMBOL);
  17. }
  18. /** * TODO 获取运算等级 * * @param str * @return int */
  19. public static int getLevel(String str) {
  20. if (Constants.ADD.equals(str) || Constants.MINUS.equals(str)) {
  21. return Constants.LEVEL_1;
  22. } else if (Constants.TIMES.equals(str) || Constants.DIVISION.equals(str)) {
  23. return Constants.LEVEL_2;
  24. }
  25. return Constants.LEVEL_HIGH;
  26. }
  27. }

编写核心的计算方法。

  1. public class Calculator {
  2. /** * 初始化栈 */
  3. public static Stack<String> stack = new Stack<>();
  4. /** * Collections.synchronizedList:集合的线程安全包装方法 */
  5. public static List<String> data = Collections.synchronizedList(new ArrayList<>());
  6. /** * TODO 匹配 * * @param str * @return java.util.List<java.lang.String> */
  7. public static List<String> doMatch(String str) {
  8. if (str == null || "".equals(str.trim())) {
  9. throw new RuntimeException("data is empty");
  10. }
  11. if (!CommonUtils.isNumber(str.charAt(0) + "")) {
  12. throw new RuntimeException("data illegal,start not with a number");
  13. }
  14. str = CommonUtils.replaceAllBlank(str);
  15. String each;
  16. int start = 0;
  17. for (int i = 0; i < str.length(); i++) {
  18. if (CommonUtils.isSymbol(str.charAt(i) + "")) {
  19. each = str.charAt(i) + "";
  20. //栈为空,( 操作符,或者 操作符优先级大于栈顶优先级 && 操作符优先级不是( )的优先级 及 是 ) 不能直接入栈
  21. boolean flag = stack.isEmpty() || Constants.LEFT.equals(each) || ((CommonUtils.getLevel(each) > CommonUtils.getLevel(stack.peek())) && CommonUtils.getLevel(each) < Constants.LEVEL_HIGH);
  22. if (flag) {
  23. stack.push(each);
  24. } else if (!stack.isEmpty() && CommonUtils.getLevel(each) <= CommonUtils.getLevel(stack.peek())) {
  25. //栈非空,操作符优先级小于等于栈顶优先级时出栈入列,直到栈为空,或者遇到了(,最后操作符入栈
  26. while (!stack.isEmpty() && CommonUtils.getLevel(each) <= CommonUtils.getLevel(stack.peek())) {
  27. if (CommonUtils.getLevel(stack.peek()) == Constants.LEVEL_HIGH) {
  28. break;
  29. }
  30. data.add(stack.pop());
  31. }
  32. stack.push(each);
  33. } else if (Constants.RIGHT.equals(each)) {
  34. // ) 操作符,依次出栈入列直到空栈或者遇到了第一个)操作符,此时)出栈
  35. while (!stack.isEmpty() && Constants.LEVEL_HIGH >= CommonUtils.getLevel(stack.peek())) {
  36. if (Constants.LEVEL_HIGH == CommonUtils.getLevel(stack.peek())) {
  37. stack.pop();
  38. break;
  39. }
  40. data.add(stack.pop());
  41. }
  42. }
  43. // 前一个运算符的位置
  44. start = i;
  45. } else if (i == str.length() - 1 || CommonUtils.isSymbol(str.charAt(i + 1) + "")) {
  46. each = start == 0 ? str.substring(start, i + 1) : str.substring(start + 1, i + 1);
  47. if (CommonUtils.isNumber(each)) {
  48. data.add(each);
  49. continue;
  50. }
  51. throw new RuntimeException("data not match number");
  52. }
  53. }
  54. //如果栈里还有元素,此时元素需要依次出栈入列,可以想象栈里剩下栈顶为/,栈底为+,应该依次出栈入列,可以直接翻转整个 stack 添加到队列
  55. Collections.reverse(stack);
  56. data.addAll(new ArrayList<>(stack));
  57. System.out.println(data);
  58. return data;
  59. }
  60. /** * TODO 算出结果 * * @param list * @return java.lang.Double */
  61. public static Double calculateResult(List<String> list) {
  62. double res = 0D;
  63. if (list == null || list.isEmpty()) {
  64. return null;
  65. }
  66. if (list.size() == 1) {
  67. System.out.println(list);
  68. res = Double.parseDouble(list.get(0));
  69. return res;
  70. }
  71. ArrayList<String> list1 = new ArrayList<>();
  72. for (int i = 0; i < list.size(); i++) {
  73. list1.add(list.get(i));
  74. if (CommonUtils.isSymbol(list.get(i))) {
  75. Double d = doTheMath(list.get(i - 2), list.get(i - 1), list.get(i));
  76. list1.remove(i);
  77. list1.remove(i - 1);
  78. list1.set(i - 2, d + "");
  79. list1.addAll(list.subList(i + 1, list.size()));
  80. break;
  81. }
  82. }
  83. calculateResult(list1);
  84. return res;
  85. }
  86. /** * TODO 运算 * * @param str1 * @param str2 * @param symbol * @return java.lang.Double */
  87. public static Double doTheMath(String str1, String str2, String symbol) {
  88. Double result;
  89. switch (symbol) {
  90. case Constants.ADD:
  91. result = new BigDecimal(str1).add(new BigDecimal(str2)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
  92. break;
  93. case Constants.MINUS:
  94. result = new BigDecimal(str1).subtract(new BigDecimal(str2)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
  95. break;
  96. case Constants.TIMES:
  97. result = new BigDecimal(str1).multiply(new BigDecimal(str2)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
  98. break;
  99. case Constants.DIVISION:
  100. result = new BigDecimal(str1).divide(new BigDecimal(str2)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
  101. break;
  102. default:
  103. result = null;
  104. }
  105. return result;
  106. }
  107. }

编写测试类。

  1. public static void main(String[] args) {
  2. String math = "9+(3-1)*3+10/2";
  3. try {
  4. calculateResult(doMatch(math));
  5. } catch (Exception e) {
  6. e.printStackTrace();
  7. }
  8. }

输出结果:

  1. [9, 3, 1, -, 3, *, +, 10, 2, /, +]
  2. [20.0]

逆波兰计算器的实现就到这里结束了,其中存在很多问题。如果想实现一个完整的计算器,还有很多细节需要考虑进去。数据结构也是一步一步学习的。慢慢理解才能变的透彻。

参考资料:
作者:韩顺平
课程:《Java数据结构与算法》

发表评论

表情:
评论列表 (有 0 条评论,185人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java代码实现波兰计算器

    关于逆波兰计算器,需求如下 输入一个逆波兰表达式(后缀表达式),使用栈(Stack),计算其结果,只支持对整数的计算即可。 思路分析 后缀表达式又称逆波兰表达式,与

    相关 波兰计算器

    该版本只支持整数类型数进行计算 本文主要是对逆波兰计算器的原理介绍和代码实现,所以对前缀、中缀、后缀表达式不做过多的赘述,下面先简单介绍一下后缀表达式 后缀表达式又称逆波兰

    相关 波兰计算器

    > 逆波兰表达式又叫做后缀表达式,逆波兰表达式把运算量写在前面,把运算符写在后面。 我们平时书写的表达式都是中缀表达式,这个表达式更加方便我们进行计算。但是相对于计算机而言,

    相关 波兰计算器

    一 整体算法 1 输入一个中缀表达式。 2 将中缀表达式转换为后缀表达式(逆波兰表达式)。 3 根据逆波兰表达式求算最后的结果。 二 将中缀表达式转换为后缀表达式

    相关 波兰计算器完整版

    一 需求 完整版的逆波兰计算器,功能包括如下: 1 支持 + - \ / ( ) 2 多位数,支持小数 3 兼容处理, 过滤任何空白字符,包括空格、制表符、换页符