栈的最后表演:逆波兰表达式求值

前言

今天刷题遇到了逆波兰表达式,死亡的记忆突然开始攻击我,好嘛,既然根基不牢,那么就一次性给他搞明白了!

一、算术表达式求值

算术表达式又叫中缀表达式,如果直接给出一个中缀表达式让我们求值,当然并不是不可以,只不过说会比较麻烦。就拿四则运算来说我们既要考虑“括号”又要考虑运算符的“优先级”。当然对于括号问题和优先级问题我们借助递归和栈都能够解决:

public int solve (String s) {
    // write code here
    s = s.trim();
    Deque<Integer> stack = new ArrayDeque<>();
    // 初始化字符、符号
	int number = 0;
	char sign = '+';
	char[] charArray = s.toCharArray();
	for (int i = 0; i < charArray.length; i ++) {
		// 获取当前字符
		char c = s.charAt(i);
		if (c == ' ') {
			// 空字符是无效字符直接跳过
			continue;
		}
		if (Character.isDigit(c)) {
			// 遇到数字时继续遍历求这个完整的数字的值,保存到 number 中(如“10”)
			number = number * 10 + c - '0';
		}
		if (c == '(') {
			// 遇到左括号时递归求这个括号里面的表达式的值
			// 先遍历找到对应的右括号,因为可能里面还嵌有多对括号,
			// 使用一个变量 counterPartition 统计括号对数直到变量为 0
			int j = i + 1;
            int counterPartition = 1;
            while (counterPartition > 0) {
                if (charArray[j] == '(') {
                    counterPartition++;
                }
                if (charArray[j] == ')') {
                    counterPartition--;
                }
                j++;
            }
            number = solve(s.substring(i + 1, j - 1));
            i = j - 1;
		}
		if (!Character.isDigit(c) || i == charArray.length - 1) {
			// 遇到符号字符时,包括遍历到末尾时都需要:
			// 1.根据上一个运算符并把计算结果 push 进栈
			// 2.然后保存新的运算符到 sign
			if (sign == '+') {
                stack.push(number);
            } else if (sign == '-') {
                stack.push(-1 * number);
            } else if (sign == '*') {
                stack.push(stack.pop() * number);
            } else if (sign == '/') {
                stack.push(stack.pop() / number);
            }
            number = 0;
            sign = c;
		}
	}
	// 用栈保存各部分计算的和,最后把栈中的结果求和即可
	int ans = 0;
    while (!stack.isEmpty()) {
        ans += stack.pop();
    }
    return ans;
}

虽然我们上面使用栈也可以解决算术(中缀)表达式求值的问题,但是整个过程肉眼可见的麻烦,那么能不能更简单有效的处理表达式求值呢?逆波兰表达式应运而生。

二、中缀表达式转逆波兰表达式

逆波兰表达式是一种不需要括号的后缀表达法,它的特点就是所有的符号都是在要运算数的后面出现。比如:“9+(3-1)*3+10/2” 转换成逆波兰表达式就成了:“9 3 1- 3*+ 10 2 /+”。显然,这里没有括号,对于习惯了算术(中缀)表达式的我们来说,这样的表述是很难受的。不过我们不喜欢,有机器喜欢,比如我们聪明的计算机。

在具体介绍如何使用逆波兰表达式求值之前,你肯定跟我一样好奇:算术表达式和逆波兰表达式之间是如何转换的?下面我们就来进行解密。

1、转换规则

从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。

2、代码实现:

    // 中缀表达式转逆波兰表达式
    // 定义运算符优先级
    private static final Map<Character, Integer> precedence = new HashMap<>();
    static {
        precedence.put('+', 1);
        precedence.put('-', 1);
        precedence.put('*', 2);
        precedence.put('/', 2);
        precedence.put('^', 3);
    }

    public static String infixToRPN(String infix) {
        StringBuilder output = new StringBuilder(); // 存放输出结果
        Stack<Character> stack = new Stack<>(); // 运算符栈

        for (char c : infix.toCharArray()) {
            if (Character.isDigit(c)) { // 如果是数字,直接输出
                output.append(c);
            } else if (c == '(') { // 如果是左括号,入栈
                stack.push(c);
            } else if (c == ')') { // 如果是右括号,将左括号之前的运算符全部输出
                while (!stack.isEmpty() && stack.peek() != '(') {
                    output.append(stack.pop());
                }
                if (!stack.isEmpty()) {
                    stack.pop(); // 弹出左括号
                }
            } else { // 如果是运算符
                while (!stack.isEmpty() && stack.peek() != '(' && precedence.get(c) <= precedence.get(stack.peek())) {
                    output.append(stack.pop()); // 将优先级高于等于当前运算符的运算符全部输出
                }
                stack.push(c);
            }
        }

        while (!stack.isEmpty()) { // 将剩余的运算符全部输出
            output.append(stack.pop());
        }

        return output.toString();
    }

三、逆波兰表达式求值

1、求值规则

规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号,就将处于栈顶两个数字出栈,进行运算,运算结果进栈, 直到最终获得结果。

2、代码实现

public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for (String x:tokens) {
            if (!x.equals("+") && !x.equals("-") && !x.equals("*") && !x.equals("/")) {
                stack.push(Integer.parseInt(x));
            } else {
                int num1 = stack.pop();
                int num2 = stack.pop();
                switch (x) {
                    case "+":
                        stack.push(num2+num1);
                        break;
                    case "-":
                        stack.push(num2-num1);
                        break;
                    case "*":
                        stack.push(num2*num1);
                        break;
                    case "/":
                        stack.push(num2/num1);
                        break;
                }
            }
        }
        return stack.pop();
    }

四、拓展思考

有前缀表达式吗?当然是有的,前缀表达式就是所谓的波兰表达式,它的转换与求值和后缀表达式不同:后缀表达式的转换和运求值扫描顺序都是从左往右,而前缀表达式的转换和求值是从右向左,其他规则一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不摸鱼的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值