【OJ - stack】中缀表达式转后缀表达式 & 后缀(逆波兰)表达式的计算

416 阅读4分钟

OJ - 中缀表达式转后缀表达式

题目难度:简单

OJ链接:中缀表达式转后缀表达式_牛客网 (nowcoder.com)

将中缀表达式转为后缀表达式(逆波兰表达式),输入 a+b*c/d-a+f/b 输出 abc*d/+a-fb/+

要求:语言不限;输入输出均为单个字符串;操作数用单个小写字母表示,操作符只需支持 +-*/,按照四则运算顺序确定优先级,不包含括号

示例

输入:a+b*c/d-a+f/b
输出:abc*d/+a-fb/+  // 操作符跟在它要运算的操作数的后面,按优先级排列

解题思路

不包含括号的中缀转后缀 - 算法思路:比如:1 + 2 * 3

  • 定义栈 st

  • 遍历中缀表达式:

    1. 遇到操作数,直接输出(存储),作为后缀表达式的一部分。

    2. 遇到操作符 s,入栈前先判断下:

      如果栈不为空,且 s 的优先级 低于或等于 栈顶操作符的优先级,则先将栈顶的操作符出栈(重复此步骤,继续与栈顶的操作符比较,直到栈为空,或者 s 的优先级 高于 栈顶的操作符,才停止比较)

      然后再将当前操作符 s 入栈。

  • 遍历结束后,依次弹出栈中剩余的所有操作符,得到后缀表达式。

image-20220519162737781 image-20220519163906610

代码如下:不包含括号的中缀转后缀

#include<iostream>
#include<string>
#include<stack>
using namespace std;

// 判断优先级(s1是否高于s2)
bool Priority(char s1, char s2) 
{
    if (s1 == '*' || s1 == '/')
        if (s2 == '+' || s2 == '-') return true;
    
    return false;
}

int main()
{
    string str;
    cin >> str; // 读入中缀表达式

    stack<char> st; // 定义一个栈

    string ret; // 存放后缀表达式

    // 遍历中缀表达式
    for (const auto& s : str) {
        if (s == '+' || s == '-' || s == '*' || s == '/') // 操作符
        { 
            // 操作符s在入栈前,先判断下
            // 如果栈不为空,且s的优先级低于或等于栈顶操作符的优先级,则先将栈顶的操作符出栈
            while (!st.empty() && !Priority(s, st.top())) {
                ret.push_back(st.top()); // 栈顶操作符出栈(存储到ret中)
                st.pop();
            }
            // 直到栈为空,或者s的优先级高于栈顶的操作符,才停止循环
            
            st.push(s); // 然后将当前操作符s入栈
        }
        else // 操作数 
        {
            ret.push_back(s); // 直接输出(存储到ret中)
        }
    }
    // 遍历结束后,依次弹出栈中剩余的所有操作符
    while (!st.empty()) 
    {
        ret.push_back(st.top());
        st.pop();
    }

    // 输出后缀表达式
    cout << ret << endl;

    return 0;
}

更加复杂的中缀表达式转后缀表达式:

包含括号的中缀转后缀 - 算法思路:比如:1 + 2 * ( 3 - 4 ) + 5

  • 定义栈 st

  • 遍历中缀表达式:

    1. 遇到操作数,直接输出(存储),作为后缀表达式的一部分。

    2. 遇到操作符 s,入栈前先判断下:

      如果栈不为空,且 s 的优先级 低于或等于 栈顶操作符的优先级,则先将栈顶的操作符出栈(重复此步骤,继续与栈顶的操作符比较,直到栈为空,或者 s 的优先级 高于 栈顶的操作符,才停止比较)

      然后再将当前操作符 s 入栈。

    3. 遇到前括号 '(' 时,设置 flag = 1,表示括号中的所有操作符优先级被提高,遇到后括号 ')' 时,设置 flag = 0,说明括号结束了。

  • 遍历结束后,依次弹出栈中剩余的所有操作符,得到后缀表达式。

image-20220519215115266

后缀表达式的运算 - 算法思路:比如:1 2 3 * + 4 -

  • 定义栈 st
  • 遍历后缀表达式:
    1. 遇到操作数,入栈
    2. 遇到操作符,连续取出两个栈顶的数据出来,进行运算,再把运算结果入栈
  • 遍历结束,输出 st 栈顶数据,就是最终运算结果。

后缀表达式的运算 - 画图演示

image-20220519214820738

OJ - 逆波兰表达式求值(⭐中等)

题目难度:中等

OJ链接:150. 逆波兰表达式求值 - 力扣(LeetCode)

根据逆波兰表示法,求表达式的值。

有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

注意:两个整数之间的除法只保留整数部分。

可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

示例

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

知识补充:逆波兰表达式

逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。

  • 我们平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。这种表达式计算机不知道如何去计算。

  • 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。这种表达式计算机可以去计算。

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。

  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中


解题思路

程序处理中缀表达式时,不太好运算,因为运算符的优先级问题

那如何解决呢,把中缀表达式转换成后缀表达式(逆波兰表达式),然后对后缀表达式进行运算。

运算思路上面已讲解。

class Solution {
public:
    int evalRPN(vector<string>& tokens) {     

        stack<int> st; // 定义一个栈

        for (const auto& str : tokens) { // 遍历后缀表达式
            if (str == "+" || str == "-" || str == "*" || str == "/") { // 操作符
                int left = 0, right = 0; // 左右操作数
                
                right = st.top(); // 连续取出两个栈顶的操作数
                st.pop();
                left = st.top();
                st.pop();

                if (str == "+") st.push(left + right); // 运算结果入栈
                if (str == "-") st.push(left - right);
                if (str == "*") st.push(left * right);
                if (str == "/") st.push(left / right);
            }
            else { // 操作数
                st.push(stoi(str)); // 入栈(注意:要转成数字)
            }
        }

        return st.top(); // 栈顶元素即运算结果
    }
};