计算器的实现
计算器实现思路
-
我们⽇常写的计算表达式都是中缀表达式,也就是运算符在中间,运算数在两边,但是直接读取⽆ 法⻢上进⾏运算因为⼀个计算表达式还涉及运算符优先级问题。如: 都⽆法运算,因为后⾯还有括号优先级更⾼。 1-2 * ( 3-4)+5 中遇到-和*
-
所以其中⼀种实现思路是把中缀表达式转换为后缀表达式,也就是说分析计算表达式的优先级,将 运算符放到前⾯,运算符放到运算数的后⾯,然后我们依次读取后缀表达式,遇到运算符就可以进 ⾏运算了。后缀表达式也就做逆波兰表达式(ReversePolishNotation,RPN),这种表⽰法由波兰逻 辑学家J·卢卡西维兹于1929年提出,后来被⼴泛应⽤于计算机科学中。
后缀表达式进⾏运算
- 后缀表达式因为已经确定好优先级,运算符⽅式⾮常简单,就是遇到运算符时,取前⾯的两个运算 符进⾏运算,因为经过中缀转后缀优先级已经确定好了
- 建⽴⼀个栈存储运算数,读取后缀表达式,遇到运算数⼊栈,遇到运算符,出栈顶的两个数据进⾏ 运算,运算后将结果作为⼀个运算数⼊栈继续参与下⼀次的运算。读取表达式结束后,最后栈⾥⾯ 的值就是运算结果。
代码如下:
class Solution {
public:
int evalRPN(const vector<string>& tokens) {
stack<int> s;
for (size_t i = 0; i < tokens.size(); ++i)
{
const string& str = tokens[i];
// str为运算数
if (!("+" == str || "-" == str || "*" == str || "/" == str))
{
s.push(stoi(str));
}
else
{
// str为运算符
int right = s.top();
s.pop();
int left = s.top();
s.pop();
switch (str[0])
{
case '+':
s.push(left + right);
break;
case '-':
s.push(left - right);
break;
case '*':
s.push(left * right);
break;
case '/':
s.push(left / right);
break;
}
}
}
return s.top();
}
};
中缀表达式转后缀表达式
转换思路
- 依次读取计算表达式中的值,遇到运算数直接输出。
- 建⽴⼀个栈存储运算符,利⽤栈后进新出性质,遇到后⾯运算符,出栈⾥⾯存的前⾯运算符进⾏⽐ 较,确定优先级。
- 遇到运算符,如果栈为空或者栈不为空且当前运算符⽐栈顶运算符优先级⾼,则当前运算符⼊栈。 因为如果栈⾥⾯存储的是前⼀个运算符,当前运算符⽐前⼀个优先级⾼,说明前⼀个不能运算,当 前运算符也不能运算,因为后⾯可能还有更⾼优先级的运算符。
- 遇到运算符,如果栈不为为空且当前运算符⽐栈顶运算符优先级低或相等,说明栈顶的运算符可以 运算了,则输出栈顶运算符,当前运算符继续⾛前⾯遇到运算符的逻辑。
- 如果遇到(),则把括号的计算表达式当成⼀个⼦表达式,跟上思路类似,进⾏递归处理⼦表达式, 处理后转换出的后缀表达式加在前⾯表达式的后⾯即可。
- 计算表达式或者()中⼦表达式结束值,输出栈中所有运算符。
代码如下:
class Solution {
public:
//map<char, int> _operatorPrecedence = { { '+', 1 }, { '-', 1 }, { '*', 2
}, { '/', 2 } };
int operatorPrecedence(char ch)
{
struct opPD
{
char _op;
int _pd;
};
static opPD arr[] = { {'+', 1},{'-', 1},{'*', 2},{'/', 2} };
for (auto& e : arr)
{
if (e._op == ch)
{
return e._pd;
}
}
assert(false);
return -1;
}
void toRPN(const string& s, size_t& i, vector<string>& v)
{
stack<char> 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() || operatorPrecedence(s[i]) >
operatorPrecedence(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()
{
size_t 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:
//map<char, int> _operatorPrecedence = { { '+', 1 }, { '-', 1 }, { '*', 2 }, { '/', 2 } };
int operatorPrecedence(char ch)
{
struct opPD
{
char _op;
int _pd;
};
static opPD arr[] = { {'+', 1},{'-', 1},{'*', 2},{'/', 2} };
for (auto& e : arr)
{
if (e._op == ch)
{
return e._pd;
}
}
assert(false);
return -1;
}
void toRPN(const string& s, size_t& i, vector<string>& v)
{
stack<char> 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() || operatorPrecedence(s[i]) >
operatorPrecedence(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(const vector<string>& tokens) {
stack<int> s;
for (size_t i = 0; i < tokens.size(); ++i)
{
const string& str = tokens[i];
// str为数字
if (!("+" == str || "-" == str || "*" == str || "/" == str))
{
s.push(stoi(str));
}
else
{
// str为操作符
int right = s.top();
s.pop();
int left = s.top();
s.pop();
switch (str[0])
{
case '+':
s.push(left + right);
break;
case '-':
s.push(left - right);
break;
case '*':
s.push(left * right);
break;
case '/':
s.push(left / right);
break;
}
}
}
return s.top();
}
int calculate(string s)
{
// 1、去除所有空格,否则下⾯的⼀些逻辑没办法处理
std::string news;
news.reserve(s.size());
for (auto ch : s)
{
if (ch != ' ')
news += ch;
}
s.swap(news);
news.clear();
// 2、将所有的负数-x转换为0-x
for (size_t i = 0; i < s.size(); ++i)
{
if (s[i] == '-' && (i == 0 || (!isdigit(s[i - 1]) && s[i - 1] !=
')')))
news += "0-";
else
news += s[i];
}
// 中缀表达式转成后缀表达式
size_t i = 0;
vector<string> v;
toRPN(news, i, v);
// 后缀表达式进⾏运算
return evalRPN(v);
}
};