LintCode 370: Convert Expression to Reverse Polish Notation (逆波兰表达式,栈经典题)

本文详细介绍了一种高效算法,即调度场算法(Shunting Yard Algorithm),用于将中缀表达式转换为逆波兰表达式(Reverse Polish Notation, RPN)。通过使用栈和优先级映射,该算法能正确处理括号和操作符优先级,适用于多种编程场景。
  1. Convert Expression to Reverse Polish Notation
    中文English
    Given a string array representing an expression, and return the Reverse Polish notation of this expression. (remove the parentheses)

Example
Example 1:

Input: [“3”, “-”, “4”, “+”, “5”]
Output: [“3”, “4”, “-”, “5”, “+”]
Explanation: 3 - 4 + 5 = -1 + 5 = 4
3 4 - 5 + = -1 5 + = 4
Example 2:

Input: [“(”, “5”, “-”, “6”, “)”, ““, “7”]
Output: [“5”,“6”,”-“,“7”,”
”]
Explanation: (5 - 6) * 7 = -1 * 7 = -7
5 6 - 7 * = -1 7 * = -7
Clarification
Definition of Reverse Polish Notation:

https://en.wikipedia.org/wiki/Reverse_Polish_notation
https://baike.baidu.com/item/逆波兰表达式/9841727?fr=aladdin

解法1:用栈。
这题有点像二叉树的中序遍历转后序遍历。
Input: //(3-2)*7
[“(”,“3”,“-”,“2”,“)”," * “,“7”]
Output:
[“3”,“2”,”-“,“7”,” * "]

Input: //3-2*7
[“3”,“-”,“2”," * “,“7”]
Output:
[“3”,“2”,“7”,” * “,”-"]

我们知道光知道二叉树的中序遍历还不能转后序遍历,因为结果不唯一。除非是前序和中序都知道才能转后序遍历。
这里为什么可以呢?不知道是不是因为有优先级的原因,需要进一步了解一下。
更新:我个人理解是表达式的中序和后序跟通常的二叉树中序和后序不太一样。

  1. 比如说1 * 3,中序是1 * 3,后序是1 3 * . 根据中序,我们知道 * 一定是根节点,而不是1是根节点, * 是1的右节点,3是 * 的右节点
  2. 而且表达式的中序会有括号,括号里面的优先处理,这样就没有歧义了。

在中序转后序的整个过程中,数字出现的先后顺序是不变的。可以看见原来是 1、3,后序中也还是1、3。所以数字,可以用和遍历顺序相同的某个数据结构。
而符号位,它们的出现顺序,其实是和原来相反的。在一个可以运算的区间内,它们总是和原来顺序相反的。A*(B+C),就是一个"运算区间"——原来×是在+的前边,实际后序运算时是A B C + ×,+排在×的前边。这也是和后序表达式的运算顺序决定,因为它本来,也是把更优先的符号,放在离操作数更近的位置。
所以在遇到右括号之前,运算符,是可以考虑用一个栈存放的。

注意:

  1. 用一个stack和一个map,其中map存储操作符和优先级对应关系。

  2. 遍历表达式,
    a)如果字符是数字,放入结果中。
    b)如果字符是左括号,直接压栈
    c)如果字符是右括号,把栈里面的内容(不管是数字还是操作符)挨个弹出,放到结果中,直到遇到左括号,左括号也要弹出。
    d)如果字符是’+‘, ‘-’ ,’*', '/'这样的操作符,把栈里面的内容(如果是更高或相等优先级的操作符)挨个弹出,放到结果中,直到遇到一个更低优先级的操作符(这个更低优先级的操作符不动,还是在栈中)。然后将本身的这个操作符压栈。
    e)最后把栈里面的内容(不管是什么)挨个弹出,放到结果中。

  3. 为什么遇到右括号就可以不管三七二十一把栈里面左括号前的内容全部弹出呢?因为这里左右括号里面的内容应该自成一个体系,如果有操作符优先方面的问题,在2.d里面就已经解决了。

  4. 注意与LintCode369转波兰表达式相区别:这里要用>=,而LintCode 369是>。
    while(!st.empty() && (!isOperator(str) || priorityMp[st.top()] >= priorityMp[str])) {
    这里>=主要是解决同级优先级的问题。假设input=[“3”,“-”,“4”,“+”,“5”],
    如果我们用>的话,
    output=[“3”,“4”,“5”,“+”,“-”]
    但实际上output=[“3”,“4”,“-”,“5”,“+”]

  5. 举例1:
    [“3”,“-”,“2”," * “,“7”] =>[“3”,“2”,“7”,”*“,”-"]
    先将3放到结果中,遇到-时,-入栈。遇到2,将2放到结果中。遇到 ’ * ‘,此时-还在栈内,将’ * ‘压栈。遇到7,将7放入结果中。最后栈里面还剩‘ * ’和’ - ',全部弹出放到结果中。

  6. 逆波兰表达式是后序遍历,波兰表达式是前序遍历。Expression是中序遍历。波兰表达式和逆波兰表达式里面肯定都没有括号!Expression有括号(如果表达式里面本来就有括号的话)。

代码如下:

class Solution {
public:
    /**
     * @param expression: A string array
     * @return: The Reverse Polish notation of this expression
     */
     
    vector<string> convertToRPN(vector<string> &expression) {
        int n = expression.size();
        if (n == 0) return vector<string>();
        
        vector<string> result;
        stack<string> st;
        map<string, int> priorityMp; //operator, priority
        priorityMp["+"] = 1;
        priorityMp["-"] = 1;
        priorityMp["*"] = 2;
        priorityMp["/"] = 2;

        for (int i = 0; i < n; ++i) {
            
            string str = expression[i];

            // if it is a number
            if (!isOperator(str)) {
                //st.push(str);
                result.push_back(str);
            }
            else if (str == "(") st.push(str);
            else if (str == ")") {
                while(st.top() != "(") {
                    string topStr = st.top();
                    st.pop();
                    result.push_back(topStr);
                } 
                st.pop(); //pop the '('
            } 
            // the operator case +-*/
            else {
               // while(!st.empty() && priorityMp[st.top()] >= priorityMp[str]) {
                while(!st.empty() && (!isOperator(str) || priorityMp[st.top()] >= priorityMp[str])) {
                    result.push_back(st.top());
                    st.pop();
                                
                }
                st.push(str);
            }
        }

        while(!st.empty()) {
            string topStr = st.top();
            st.pop();
            result.push_back(topStr);
        }
        
        return result;
    }

private:
    bool isOperator(string s) {
        return (s.size() == 1 && !isdigit(s[0]));
    }
};

二刷: 跟上面类似的方法。这类方法叫调度场算法(Shunting Yard Algorithm),下面几个链接讲得好。
https://boycgit.github.io/algorithm-shunting-yard/
https://old-panda.com/2020/11/29/shunting-yard-algorithm/
下面这个算法用了两个栈。

class Solution {
public:
    /**
     * @param expression: A string array
     * @return: The Reverse Polish notation of this expression
     */
    vector<string> convertToRPN(vector<string> &expression) {
        int len = expression.size();
        map<string, int> priority;
        priority["+"] = 1;
        priority["-"] = 1;
        priority["*"] = 2;
        priority["/"] = 2;
        //priority["("] = 0;
        //priority[")"] = 0;
        stack<string> RPNStack, optrStk;
        int resLen = len;
        for (int i = 0; i < len; ++i) {
            string expStr = expression[i];
            if (expStr.size() > 1 || (expStr[0] >= '0' && expStr[0] <= '9')) {
                RPNStack.push(expStr);
                continue;
            }
            if (expStr == "(") {
                optrStk.push(expStr);
                resLen--;
                continue;
            }
            if (expStr == ")") {
                while (!optrStk.empty() && optrStk.top() != "(") {
                    string topStr = optrStk.top();
                    optrStk.pop();
                    RPNStack.push(topStr);
                }    
                optrStk.pop(); //pop the "("
                resLen--;
                continue;
            }
            while (!optrStk.empty() && priority[optrStk.top()] >= priority[expStr]) {
                string topStr = optrStk.top();
                optrStk.pop();
                RPNStack.push(topStr);
            }
            optrStk.push(expStr);
        }
        while (!optrStk.empty()) {
            string topStr = optrStk.top();
            optrStk.pop();
            RPNStack.push(topStr);
        }
        vector<string> res(resLen);
        while (!RPNStack.empty()) {
            string topStr = RPNStack.top();
            RPNStack.pop();
            res[--resLen] = topStr;
        }
        return res;
    }
};

其实用一个栈就可以了,因为如果我们不evaluate RPN(evalutate 逆波兰表达式,见Lintcode 424)的话,RPNStack只有压栈操作,没有出栈操作,那就用一个vector就可以了。解法如下:

class Solution {
public:
    /**
     * @param expression: A string array
     * @return: The Reverse Polish notation of this expression
     */
    vector<string> convertToRPN(vector<string> &expression) {
        int len = expression.size();
        map<string, int> priority;
        priority["+"] = 1;
        priority["-"] = 1;
        priority["*"] = 2;
        priority["/"] = 2;
        //priority["("] = 0;
        //priority[")"] = 0;
        stack<string> optrStk;
        vector<string> res;
        for (int i = 0; i < len; ++i) {
            string expStr = expression[i];
            if (expStr.size() > 1 || (expStr[0] >= '0' && expStr[0] <= '9')) {
                res.push_back(expStr);
                continue;
            }
            if (expStr == "(") {
                optrStk.push(expStr);
                continue;
            }
            if (expStr == ")") {
                while (!optrStk.empty() && optrStk.top() != "(") {
                    res.push_back(optrStk.top());
                    optrStk.pop();
                }    
                optrStk.pop(); //pop the "("
                continue;
            }
            while (!optrStk.empty() && priority[optrStk.top()] >= priority[expStr]) {
                res.push_back(optrStk.top());
                optrStk.pop();
            }
            optrStk.push(expStr);
        }
        while (!optrStk.empty()) {
            res.push_back(optrStk.top());
            optrStk.pop();
        }
        return res;
    }
};

解法2:用递归,每次找到优先级最低的运算符,然后输出左边的内容,输出右边的内容,再输出运算符的内容。

class Solution {
public:
    /**
     * @param expression: A string array
     * @return: The Reverse Polish notation of this expression
     */
    vector<string> convertToRPN(vector<string> &expression) {
        vector<string> res;
        int expSize = expression.size();
        res = evaluate(expression, 0, expSize - 1);
        return res;        
    }
private:
    vector<string> evaluate(vector<string> &expression, int start, int end) {
        vector<string> res;
        if (start > end) return res;
        
        #define INF 0x3f3f3f3f
        int lowestPrio = INF - 1; //这里是针对运算符的优先级而言,比常数项的优先级低一点就可以。
        int lowestPrioPos = -1;
        int expSize = expression.size();
        int tmpPrio = 0;
        for (int i = start; i <= end; i++) {
            string curStr = expression[i];
            int curPrio = INF; //注意这里默认常数项的优先级无限高。
            if (curStr == "(") {
                tmpPrio += 100;
            } else if (curStr == ")") {
                tmpPrio -= 100;
            } else if (curStr == "+" || curStr == "-") {
                curPrio = tmpPrio + 1;
            } else if (curStr == "*" || curStr == "/") {
                curPrio = tmpPrio + 2;
            }
            if (curPrio <= lowestPrio) {
                lowestPrio = curPrio;
                lowestPrioPos = i;
            }
        }
        if (lowestPrioPos == -1) { //It is a number. 
            int left = start, right = end;
           // while (left <= right && (expression[left] == "(" || expression[left] == ")")) left++;
           // while (left <= right && (expression[right] == "(" || expression[right] == ")")) right--;
            while (left <= right && expression[left] == "(") left++;
            while (left <= right && expression[right] == ")") right--;
            //here if not all brackets, left and right should be the same
            if (left != right) return {};
            return {expression[left]};
        }
       
        vector<string> leftEvaluate = evaluate(expression, start, lowestPrioPos - 1);
        vector<string> rightEvaluate = evaluate(expression, lowestPrioPos + 1, end);
        res.insert(res.end(), leftEvaluate.begin(), leftEvaluate.end());
        res.insert(res.end(), rightEvaluate.begin(), rightEvaluate.end());
        res.push_back(expression[lowestPrioPos]);
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值