目录
中缀、后缀表达式简介
中缀转后缀的规则
模拟中缀转后缀
中缀转后缀代码
后缀表达式求值
后缀表达式求值代码
Leetcode相关题目
中缀、后缀表达式简介
首先说说什么是中缀表达式,中缀表达式中,操作符是以中缀形式处于操作数的中间。例如,在表达式“3 + 4”中,“+”是中缀操作符,位于两个操作数“3”和“4”之间。对于我们而言,它最直观。下面说说后缀表达式,
逆波兰表达式(Reverse Polish Notation,RPN),也被称为后缀表达式,是一种算术表达式的表示方法。在这种表示法中,运算符被写在操作数的后面,而不是像常规的中缀表达式那样写在操作数的中间。逆波兰表达式不使用括号来指示运算的优先级,而是完全依赖于运算符出现的顺序以及运算符本身的优先级规则。也就是说,后缀表达式严格遵循从左往右计算。
中缀转后缀的规则
下面是中缀转后缀的思维导图,有一些细节不方便体现,我们到代码实现部分在谈。
模拟中缀转后缀
根据上面思维导图中的规则,我们来模拟一下中缀转后缀。
希望通过上面的一个简单例子,我们明白了中缀转后缀的规则。上面被框起来的部分,是个细节,我在写代码的时候犯了这样的错误——我在把+出栈后,直接让-入栈,可把我坑惨了。出现错误后,我再次看了一遍代码,重点看了这一部分,但是觉得很对,一点都没错,哎。所以,我错在哪了?为什么不可以直接让-进栈?
我们在思考这个问题的时候不要局限于上面的图,因为照着上面的话感觉我犯的错误很对。
我错就错在我以为栈内就只有+这个运算符,+运算符出栈后,栈为空,所以直接把 - 运算符入栈。但实际上,栈内可能还有其他运算符,而且优先级低于或等于栈顶运算符。此时,应该让栈顶运算符出栈,而不是把 - 直接丢入栈内。也就是说,当前运算符 - 还要继续走前面运算符的逻辑。如果这句话暂时不能理解,没关系,看了代码就明白了。
下面我们上个看起来比较复杂的式子练练手。
其中,绿色的栈是递归时创建的。上面的例子值得好好理解。
中缀转后缀代码
好了,我就不再废话了,上代码!
#include <iostream>
#include <vector>
#include <stack>
#include <map>
using namespace std;//下面需要比较优先级的高低,由于无法直接使用ASCII比较,故进行一下定义
map<char, int> pro = { {'+', 1}, {'-', 1}, {'*', 2}, {'/', 2} };void toRPN(const string& s, size_t& i, vector<string>& v)
{int n = s.size();stack<char> st;//存储操作符while (i < n){//isdigit为C语言的一个函数,功能是判断字符串中的某个字符是否是数字if (isdigit(s[i])){//细节:数字不一定是1位数,所以需要提取出完整的数字string tmp;while (i < n && isdigit(s[i])) tmp += s[i++];v.push_back(tmp);}else if (s[i] == '('){//递归处理i++;//跳过'('toRPN(s, i, v);}else if (s[i] == ')'){//结束递归while (!st.empty()){v.push_back(string(1, st.top()));//查文档——string的构造函数st.pop();}i++;//跳过')'return;}else{//走到这说明就是操作符了if (st.empty() || pro[st.top()] < pro[s[i]])st.push(s[i++]);else{v.push_back(string(1, st.top()));st.pop();//这里不能让i++,之前解释过了}}}//走到这,说明计算式或子表达式结束了,输出栈中所有运算符while (!st.empty()){v.push_back(string(1, st.top()));st.pop();}
}int main()
{vector<string> v;size_t i = 0;string s = "1+2-(3*4+5)-7";toRPN(s, i, v);for (auto s : v) cout << s << " ";cout << endl;return 0;
}
到目前,你可能会疑惑会质疑,空格不需要处理吗?或者说有空格的话上面程序会不会崩掉?
空格是需要处理的,上面的函数是在处理完原字符串中的空格后再调用的,因为我不想在该函数内部处理空格,如果你想的话也可以,多加个判断就好了。
除了这个问题,代码中还有一个细节——仔细看看toRPN函数的参数。
我是不是都用了引用?因为我们希望程序在递归时处理的也是相同的字符串,递归时处理的结果也可以影响到上层,这是我们想要的。换句话说,s,i,v,为整个程序共用。
后缀表达式求值
前面在介绍后缀表达式的时候提到,后缀表达式严格按照从左到右计算。在计算后缀表达式时,我们需要借助一个栈(当然你可以用其他数据结构模拟栈)来存储数字。
1 2 + 3 4 * 5 + - 7 -,以此表达式为例。
从左到右遍历表达式。
1)遇到运算数,直接入栈。
2)遇到操作符,取出栈中的两个操作数运算,运算结果存入中栈中。(细节下面说)
3)重复以上步骤,当遍历完表达式,栈顶元素即为答案。
有些操作符,比如 ‘-’ 和 ‘/’,它们是区分左右操作数的,即a - b 和 b - a 的结果可能不一样。这该怎么办?
栈的性质——后进先出。我们从左往右遍历,一定是左操作数先进栈,右操作数后进栈,所有先出的是右操作数,后出的是左操作数,这样就可以区分左右操作数了。其实,这就是选择栈来存储数据的原因。
后缀表达式求值代码
int calculate(vector<string>& s)
{stack<int> st;for (auto str : s){if (str == "+" || str == "-" || str == "*" || str == "/"){int right = st.top();st.pop();int left = st.top();st.pop();int ans = 0;switch (str[0]){case '+': ans = left + right; break;case '-': ans = left - right; break;case '*': ans = left * right; break;case '/': ans = left / right; break;}st.push(ans);}else{st.push(stoi(str));}}return st.top();
}
int main()
{vector<string> v;size_t i = 0;string s = "1+2-(3*4+5)-7";toRPN(s, i, v);cout << calculate(v) << endl;return 0;
}
Leetcode相关题目
. - 力扣(LeetCode)
上面这题可以我们本片博客讲到的方法解决,虽然不是最简洁的解法,但是可以练练我们上面讲到的方法。代码我在下面贴一份。
class Solution {
public:int calculate(string s) {vector<string> v;stack<int> st;size_t i = 0;toRPN(s, i, v);for(auto& str : v){if(str == "+" || str == "-" || str == "*" || str == "/"){int right = st.top();st.pop();int left = st.top();st.pop();int ans = 0;switch(str[0]){case '+': ans = left + right; break;case '-': ans = left - right; break;case '*': ans = left * right; break;case '/': ans = left / right; break;}st.push(ans);}else{st.push(stoi(str));}}return st.top();}//中缀转后缀map<char, int> pro = { {'+', 1}, {'-', 1}, {'*', 2}, {'/',2} };void toRPN(const string& s, size_t i, vector<string>& v){int n = s.size();stack<char> st;while(i < n){if(s[i] == ' ') i++;else if(isdigit(s[i])){//提取数字string tmp;while(i < n && isdigit(s[i])) tmp += s[i++];v.push_back(tmp);}else{if(st.empty() || pro[s[i]] > pro[st.top()])st.push(s[i++]);else{v.push_back(string(1, st.top()));st.pop();}}}while(!st.empty()){v.push_back(string(1, st.top()));st.pop();}}
};
本篇文章到这就结束啦~如有错误,请您不吝指出,谢谢!