逆波兰计算器

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

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

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

中缀表达式转后缀表达式

步骤如下:

  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 –

代码实现

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

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

public class Constants {

    public static final String ADD_SYMBOL = "+";

    public static final String SUB_SYMBOL = "-";

    public static final String MUL_SYMBOL = "*";

    public static final String DIV_SYMBOL = "/";
}

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

public class Operation {

    /**
     * 加
     */
    private static int ADD = 1;
    /**
     * 减
     */
    private static int SUB = 1;
    /**
     * 乘
     */
    private static int MUL = 2;
    /**
     * 除
     */
    private static int DIV = 2;

    /**
     * TODO 返回对应的优先级
     */
    public static int getValue(String operand) {
        int result = 0;
        switch (operand) {
            case Constants.ADD_SYMBOL:
                result = ADD;
                break;
            case Constants.SUB_SYMBOL:
                result = SUB;
                break;
            case Constants.MUL_SYMBOL:
                result = MUL;
                break;
            case Constants.DIV_SYMBOL:
                result = DIV;
                break;
            default:
                System.out.println("不存在该运算符");
                break;
        }
        return result;
    }
}

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

public class ReversePolandCalculator {

    /**
     * TODO 将中缀表达式转 list 集合
     *
     * @param str
     * @return java.util.List<java.lang.String>
     */
    public static List<String> infixExpressionToList(String str) {
        List<String> list = new ArrayList<>();
        // 作为一个指针
        int index = 0;
        int zeroAscii = 48;
        int nineAscii = 57;
        // 如果这个操作数是多位数
        StringBuilder mStr;
        // 存放每一个字符
        char ch;
        do {
            // 如果 ch 是一个 非数字,需要加入到 list(0-9的ASCII值是48-57)
            if ((ch = str.charAt(index)) < zeroAscii || (ch = str.charAt(index)) > nineAscii) {
                list.add("" + ch);
                // 指针后移
                index++;
            } else {
                // 如果是一个数,需要考虑多位数
                mStr = new StringBuilder();
                while (index < str.length() && (ch = str.charAt(index)) >= zeroAscii && (ch = str.charAt(index)) <= nineAscii) {
                    mStr.append(ch);
                    index++;
                }
                list.add(mStr.toString());
            }
        } while (index < str.length());
        return list;
    }

    /**
     * TODO 将 中缀表达式的 List 集合转 后缀表达式
     *
     * @param infixList
     * @return java.util.List<java.lang.String>
     */
    public static List<String> infixToSuffixExpressionList(List<String> infixList) {
        // 符号栈
        Stack<String> stack = new Stack<>();
        // 存储中间结果的
        List<String> resultList = new ArrayList<>();
        String leftBracket = "(";
        String rightBracket = ")";

        // 遍历
        for (String item : infixList) {
            // 如果是一个数,加入 resultList
            if (item.matches("\\d+")) {
                resultList.add(item);
            } else if (item.equals(leftBracket)) {
                stack.push(item);
            } else if (item.equals(rightBracket)) {
                // 如果是 右括号,则依次弹出 stack 栈顶的运算符,并压入 resultList,直到遇到左括号为止,此时将这一对括号丢弃
                while (!stack.peek().equals(leftBracket)) {
                    resultList.add(stack.pop());
                }
                // 将 ( 弹出 stack 栈,消除小括号
                stack.pop();
            } else {
                // 当 item 的优先级小于等于 stack栈顶运算符,将 stack 栈顶的运算符弹出并加入到 resultList 中,再次与 stack 中新的栈顶运算符相比较
                while (stack.size() != 0 && Operation.getValue(stack.peek()) >= Operation.getValue(item)) {
                    resultList.add(stack.pop());
                }
                // 将 此时 的 item压入栈
                stack.push(item);
            }
        }

        // 将 stack 中剩余的运算符依次弹出并加入到 resultList
        while (stack.size() != 0) {
            resultList.add(stack.pop());
        }
        // 因为 存放到 List中,因此按顺序输出就是 对应的 后缀表达式
        return resultList;
    }

    /**
     * TODO 计算
     *
     * @param expression
     * @return int
     */
    public static int calculate(List<String> expressionList) {
        // 创建栈
        Stack<String> stack = new Stack<>();
        // 遍历
        for (String item : expressionList) {
            // 使用正则取出数(匹配多位数)
            if (item.matches("\\d+")) {
                // 入栈
                stack.push(item);
            } else {
                // pop 出两个数,并运算,再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                switch (item) {
                    case Constants.ADD_SYMBOL:
                        res = num1 + num2;
                        break;
                    case Constants.SUB_SYMBOL:
                        res = num1 - num2;
                        break;
                    case Constants.MUL_SYMBOL:
                        res = num1 * num2;
                        break;
                    case Constants.DIV_SYMBOL:
                        res = num1 / num2;
                        break;
                    default:
                        throw new RuntimeException("运算符有误");
                }

                // 把 res 入栈
                stack.push("" + res);
            }
        }
        // 最后留在 stack 中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }
}

编写测试方法。

public class Test {

    public static void main(String[] args) {
        String expression = "1+((2+3)*4)-5";
        List<String> infixExpressionToList = ReversePolandCalculator.infixExpressionToList(expression);
        System.out.println("中缀表达式对应的 List=" + infixExpressionToList);

        List<String> suffixExpressionList = ReversePolandCalculator.infixToSuffixExpressionList(infixExpressionToList);
        System.out.println("后缀表达式对应的 List=" + suffixExpressionList);

        // 计算表达式的值
        System.out.printf("expression=%d", ReversePolandCalculator.calculate(suffixExpressionList));
    }
    
}

输出结果:

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

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

逆波兰计算器完成版

创建一个常量类

public class Constants {

    public static final String LEFT = "(";

    public static final String RIGHT = ")";

    public static final String ADD = "+";

    public static final String MINUS = "-";

    public static final String TIMES = "*";

    public static final String DIVISION = "/";

    /**
     * 运算符级别: + -
     */
    public static final int LEVEL_1 = 1;

    /**
     * 运算符级别:* /
     */
    public static final int LEVEL_2 = 2;

    /**
     * int 的 最大值(代表括号的等级)
     */
    public static final int LEVEL_HIGH = Integer.MAX_VALUE;

}

提取公共方法

public class CommonUtils {

    /**
     * TODO 匹配运算符
     */
    public static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)";
    private static final Pattern PATTERN = Pattern.compile("^[-\\+]?[.\\d]*$");

    /**
     * TODO 去除所有 空白字符
     *
     * @param str
     * @return java.lang.String
     */
    public static String replaceAllBlank(String str) {
        // \\s+ 匹配任何空白字符,包括空格、制表符、换页符等等, 等价于[ \f\n\r\t\v]
        return str.replaceAll("\\s+", "");
    }

    /**
     * TODO 判断是不是数字
     *
     * @param str
     * @return boolean
     */
    public static boolean isNumber(String str) {
        return PATTERN.matcher(str).matches();
    }

    /**
     * TODO 判断 是不是 运算符
     *
     * @param str
     * @return boolean
     */
    public static boolean isSymbol(String str) {
        return str.matches(SYMBOL);
    }

    /**
     * TODO 获取运算等级
     *
     * @param str
     * @return int
     */
    public static int getLevel(String str) {
        if (Constants.ADD.equals(str) || Constants.MINUS.equals(str)) {
            return Constants.LEVEL_1;
        } else if (Constants.TIMES.equals(str) || Constants.DIVISION.equals(str)) {
            return Constants.LEVEL_2;
        }
        return Constants.LEVEL_HIGH;
    }
}

编写核心的计算方法。

public class Calculator {

    /**
     * 初始化栈
     */
    public static Stack<String> stack = new Stack<>();
    /**
     * Collections.synchronizedList:集合的线程安全包装方法
     */
    public static List<String> data = Collections.synchronizedList(new ArrayList<>());

    /**
     * TODO 匹配
     *
     * @param str
     * @return java.util.List<java.lang.String>
     */
    public static List<String> doMatch(String str) {
        if (str == null || "".equals(str.trim())) {
            throw new RuntimeException("data is empty");
        }
        if (!CommonUtils.isNumber(str.charAt(0) + "")) {
            throw new RuntimeException("data illegal,start not with a number");
        }
        str = CommonUtils.replaceAllBlank(str);

        String each;
        int start = 0;
        for (int i = 0; i < str.length(); i++) {
            if (CommonUtils.isSymbol(str.charAt(i) + "")) {
                each = str.charAt(i) + "";
                //栈为空,( 操作符,或者 操作符优先级大于栈顶优先级 && 操作符优先级不是( )的优先级 及 是 ) 不能直接入栈
                boolean flag = stack.isEmpty() || Constants.LEFT.equals(each) || ((CommonUtils.getLevel(each) > CommonUtils.getLevel(stack.peek())) && CommonUtils.getLevel(each) < Constants.LEVEL_HIGH);
                if (flag) {
                    stack.push(each);
                } else if (!stack.isEmpty() && CommonUtils.getLevel(each) <= CommonUtils.getLevel(stack.peek())) {
                    //栈非空,操作符优先级小于等于栈顶优先级时出栈入列,直到栈为空,或者遇到了(,最后操作符入栈
                    while (!stack.isEmpty() && CommonUtils.getLevel(each) <= CommonUtils.getLevel(stack.peek())) {
                        if (CommonUtils.getLevel(stack.peek()) == Constants.LEVEL_HIGH) {
                            break;
                        }
                        data.add(stack.pop());
                    }
                    stack.push(each);
                } else if (Constants.RIGHT.equals(each)) {
                    // ) 操作符,依次出栈入列直到空栈或者遇到了第一个)操作符,此时)出栈
                    while (!stack.isEmpty() && Constants.LEVEL_HIGH >= CommonUtils.getLevel(stack.peek())) {
                        if (Constants.LEVEL_HIGH == CommonUtils.getLevel(stack.peek())) {
                            stack.pop();
                            break;
                        }
                        data.add(stack.pop());
                    }
                }
                // 前一个运算符的位置
                start = i;

            } else if (i == str.length() - 1 || CommonUtils.isSymbol(str.charAt(i + 1) + "")) {
                each = start == 0 ? str.substring(start, i + 1) : str.substring(start + 1, i + 1);
                if (CommonUtils.isNumber(each)) {
                    data.add(each);
                    continue;
                }
                throw new RuntimeException("data not match number");
            }
        }
        //如果栈里还有元素,此时元素需要依次出栈入列,可以想象栈里剩下栈顶为/,栈底为+,应该依次出栈入列,可以直接翻转整个 stack 添加到队列
        Collections.reverse(stack);
        data.addAll(new ArrayList<>(stack));

        System.out.println(data);
        return data;
    }

    /**
     * TODO 算出结果
     *
     * @param list
     * @return java.lang.Double
     */
    public static Double calculateResult(List<String> list) {
        double res = 0D;
        if (list == null || list.isEmpty()) {
            return null;
        }
        if (list.size() == 1) {
            System.out.println(list);
            res = Double.parseDouble(list.get(0));
            return res;
        }
        ArrayList<String> list1 = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            list1.add(list.get(i));
            if (CommonUtils.isSymbol(list.get(i))) {
                Double d = doTheMath(list.get(i - 2), list.get(i - 1), list.get(i));
                list1.remove(i);
                list1.remove(i - 1);
                list1.set(i - 2, d + "");
                list1.addAll(list.subList(i + 1, list.size()));
                break;
            }
        }
        calculateResult(list1);
        return res;
    }

    /**
     * TODO 运算
     *
     * @param str1
     * @param str2
     * @param symbol
     * @return java.lang.Double
     */
    public static Double doTheMath(String str1, String str2, String symbol) {
        Double result;
        switch (symbol) {
            case Constants.ADD:
                result = new BigDecimal(str1).add(new BigDecimal(str2)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
                break;
            case Constants.MINUS:
                result = new BigDecimal(str1).subtract(new BigDecimal(str2)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
                break;
            case Constants.TIMES:
                result = new BigDecimal(str1).multiply(new BigDecimal(str2)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
                break;
            case Constants.DIVISION:
                result = new BigDecimal(str1).divide(new BigDecimal(str2)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
                break;
            default:
                result = null;
        }
        return result;
    }
}

编写测试类。

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

输出结果:

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

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值