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

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

前言

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

一、算术表达式求值

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

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.根据上一个运算符并把计算结果 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();
    }

四、拓展思考

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

相关文章
|
2天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
38 9
|
1天前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
20 4
|
24天前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
25 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
5天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
24天前
初步认识栈和队列
初步认识栈和队列
52 10
|
18天前
数据结构(栈与列队)
数据结构(栈与列队)
15 1
|
23天前
|
存储 JavaScript 前端开发
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
61 1
|
19天前
【数据结构】-- 栈和队列
【数据结构】-- 栈和队列
13 0
|
24天前
探索顺序结构:栈的实现方式
探索顺序结构:栈的实现方式
|
24天前
|
存储 C语言
栈和队列题目练习
栈和队列题目练习
14 0