一.计算器实现思路
- 我们日常写的计算表达式都是中缀表达式,也就是运算符在中间,运算数在两边,但是直接读取无法码上进行运算因为一个计算表达式还涉及运算符优先级问题。如: 1-2*(3-4)+5 中遇到-和*都无法运算,因为后面还有括号优先级更高。
- 所以其中一种实现思路是把中缀表达式转换为后缀表达式,也就是说分析计算表达式的优先级,将运算符放到前面,运算符放到运算数的后面,然后我们依次读取后缀表达式,遇到运算符就可以进行运算了。后缀表达式也就做逆波兰表达式(Reverse Polish Notation, RPN),这种表示法由波兰逻辑学家J·卢卡西维兹于1929年提出,后来被广泛应用于计算机科学中。
二.后缀表达式(逆波兰表达式)
- 后缀表达式因为已经确定好优先级,运算符方式非常简单,就是遇到运算符时,取前面的两个运算符进行运算,因为经过中缀转后缀优先级已经确定好了。
- 建立一个栈存储运算数,读取后缀表达式,遇到运算数入栈,遇到运算符,出栈顶的两个数据进行运算,运算后将结果作为一个运算数入栈继续参与下一次的运算。读取表达式结束后,最后栈里面的值就是运算结果。
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for(auto& str : tokens)
{
if(str == "+" || str == "-" || str == "*" || str == "/")
{
//先取到的是右操作数,后取到的是做操作数
int right = st.top();
st.pop();
int left = st.top();
st.pop();
if(str[0] == '+') st.push(left + right);
else if(str[0] == '-') st.push(left - right);
else if(str[0] == '*') st.push(left * right);
else if(str[0] == '/') st.push(left / right);
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};
三.中缀表达式转后缀表达式
1.转换思路
- 依次读取计算表达式中的值,遇到运算数直接输出。
- 建立一个栈存储运算符,利用栈后进新出性质,遇到后面运算符,出栈里面存的前面运算符进行比较,确定优先级。
- 遇到运算符,如果栈为空或者栈不为空且当前运算符比栈顶运算符优先级高,则当前运算符入栈。因为如果栈里面存储的是前一个运算符,当前运算符比前一个优先级高,说明前一个不能运算,当前运算符也不能运算,因为后面可能还有更高优先级的运算符。
- 遇到运算符,如果栈不为为空且当前运算符比栈顶运算符优先级低或相等,说明栈顶的运算符可以运算了,则输出栈顶运算符,当前运算符继续走前面遇到运算符的逻辑。
- 如果遇到(),则把括号的计算表达式当成一个子表达式,跟上思路类似,进行递归处理子表达式,处理后转换出的后缀表达式加在前面表达式的后面即可。
- 计算表达式或者()中的子表达式结束值,输出栈中所有运算符。
2.代码实现
class Solution
{
//操作符的优先级
unordered_map<char, int> hashMap = { { '+', 1 }, { '-', 1 }, { '*', 2}, { '/', 2 } };
public:
//中缀转后缀
void toRPN(string& s, int& i, vector<string>& v)
{
stack<int> st;
while (i < s.size())
{
if (isdigit(s[i]))
{
//遇到操作数时:得到具体操作数并直接输出
string num;
while (i < s.size() && isdigit(s[i]))
{
num += s[i];
i++;
}
v.push_back(num);
}
else if(s[i] == '(')
{
//子表达式:递归处理
++i;
toRPN(s, i, v);
}
else if (s[i] == ')')
{
++i;
//栈中的运算符全部输出
while (!st.empty())
{
v.push_back(string(1, st.top()));
st.pop();
}
return; //结束递归
}
else
{
//遇到运算符时:
//1.如果栈为空或者栈不为空且当前运算符比栈顶运算符优先级高,说明栈顶的运算符不能运算,当前运算符入栈
//2.如果栈不为为空且比栈顶运算符优先级低或相等,说明栈顶的运算符可以运算,输出栈顶运算符
if (st.empty() || hashMap[s[i]] > hashMap[st.top()])
{
st.push(s[i]);
i++;
}
else
{
v.push_back(string(1, st.top()));
st.pop();
}
}
}
//栈中的运算符全部输出
while (!st.empty())
{
v.push_back(string(1, st.top()));
st.pop();
}
}
};
int main()
{
int i = 0;
vector<string> v;
//string str = "1+2-3";
string str = "1+2-(3*4+5)-7";
Solution().toRPN(str, i, v);
for (auto& e : v)
{
cout << e << " ";
}
cout << endl;
return 0;
}
四.计算器实现
- 有了上面两个部分计算器OJ的大部分问题就解决了,但是这里还有一些问题需要处理。因为OJ中给的中缀表达式是字符串,字符串中包含空格,需要去掉空格。
- 其次就是负数和减号,要进行区分,将所有的 “(-x” 转换为 “(0-x”
class Solution
{
public:
unordered_map<char, int> hashMap = { { '+', 1 }, { '-', 1 }, { '*', 2}, { '/', 2 } };
public:
//中缀转后缀
void toRPN(string& s, int& i, vector<string>& v)
{
stack<int> st;
while (i < s.size())
{
if (isdigit(s[i]))
{
string num;
while (i < s.size() && isdigit(s[i]))
{
num += s[i];
i++;
}
v.push_back(num);
}
else if(s[i] == '(')
{
++i;
toRPN(s, i, v);
}
else if (s[i] == ')')
{
++i;
while (!st.empty())
{
v.push_back(string(1, st.top()));
st.pop();
}
return;
}
else
{
if (st.empty() || hashMap[s[i]] > hashMap[st.top()])
{
st.push(s[i]);
i++;
}
else
{
v.push_back(string(1, st.top()));
st.pop();
}
}
}
while (!st.empty())
{
v.push_back(string(1, st.top()));
st.pop();
}
}
//计算后缀表达式
int evalRPN(vector<string>& tokens)
{
stack<int> st;
for(auto& str : tokens)
{
if(str == "+" || str == "-" || str == "*" || str == "/")
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
if(str[0] == '+') st.push(left + right);
else if(str[0] == '-') st.push(left - right);
else if(str[0] == '*') st.push(left * right);
else if(str[0] == '/') st.push(left / right);
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
int calculate(string s)
{
//去掉空格
string news;
for(auto ch : s)
{
if(ch != ' ')
{
news += ch;
}
}
//处理负数
news.swap(s);
news.clear();
for(int i = 0; i < s.size(); i++)
{
if (s[i] == '-' && (i == 0 || (s[i - 1] == '(')))
{
news += "0-";
}
else
{
news += s[i];
}
}
//中缀表达式转成后缀表达式
int i = 0;
vector<string> v;
toRPN(news, i, v);
//后缀表达式进行运算
return evalRPN(v);
}
};