背景:参加美团面试 让我求表达式12 * (3 + 4) - 6 + 8 / 2 的值 之前没接触过凉凉……
方法一:利用栈:
中缀表达式 格式:"操作数1 操作符 操作数2"
例如:12 * (3 + 4) - 6 + 8 / 2; // 中缀表达式
中缀表达式
如果要先计算操作符优先级低的两个数,比如上面要优先计算3+4,这里就必须带括号,指明计算的优先级,负责就会按照操作符默认的优先级来计算。
后缀表达式(逆波兰表达式)格式:"操作数 操作符"
上面的中缀表达式转换为后缀表达式即
例如:12 3 4 + * 6 - 8 2 / +; //后缀表达式
怎样把中缀表达式转换为后缀表达式,在下面解释,这里先看看后缀表达式是如何计算的呢?
后缀表达式如何计算
这里栈就派上用场了,从左到右一个个遍历表达式,遇到操作数就入栈,遇到操作符就依次取出栈顶的两个操作数进行计算,并把计算结果入栈,供后面计算,直到栈为空,说明表达式计算完毕,否则说明表达式有问题。过程如下图:
这样就可以按照操作符顺序计算,不用担心操作符优先级还要加大括号的问题。
那如何把“中缀表达式”转换为“后缀表达式”
思想:
-
从中缀表达式中从左往右依次取出数据
-
如遇到操作数,直接输出到后缀的队列里。
-
如果遇到操作符(包括括号),再定义一个存放操作符的栈,则:
i.如果操作符是'('
,入栈
ii.如果操作符是')'
,则把栈里的操作符依次出栈并插入到后缀序列后面,直到遇到')'
.
iii.如果操作符不是‘(’
和‘)’
,则:
(1). 如果操作符的优先级比top的优先级高,则入栈
(2).如果操作符优先级等于或小于top优先级,则将top出栈并插入到后缀序列后面,pop后,再比较栈顶元素的优先级,重复iii,直到把此操作符插入,将此操作符入栈。 -
如果中序队列里的数据已经读取完毕,记录操作符的栈里,还有操作符的话,依次出栈插入到后缀序列的后面。
此时中缀就已经转换为后缀表达式,如下图
代码:
#include<iostream>
#include<vector>
#include<stack>
#include<string>
using namespace std;
// 判断是操作符还是操作数
enum Cal_Type
{
OP_NUM,
OP_SYMBOL,
};
struct Cell
{
Cal_Type _type; // 计算类型
int _value; // 值
struct Cell(const Cal_Type& t, const int& x)
:_type(t)
, _value(x)
{
};
};
class Calculator
{
public:
Calculator(const vector<int>& exp)
:_infix(exp)
{
}
int Count()
{
Transition();
stack<int> num;
int n = _exp.size();
for (int i = 0; i < n;i++)
{
Cell cur = _exp[i];
if (cur._type == OP_NUM)
{
num.push(cur._value);
}
else if (cur._type == OP_SYMBOL)
{
int data1 = num.top();
num.pop();
int data2 = num.top();
num.pop();
int sum = 0;
switch (cur._value)
{
case '+':{ sum = data1+data2;}
break;
case '*':{ sum = data1*data2; }
break;
case '-':{ sum = data2 - data1;}
break;
case '/':{ sum = data2 / data1; }
break;
default:
break;
}
num.push(sum);
}
}
return num.top();
}
protected:
//判断是否为操作符
bool IsTypeid(char op)
{
switch (op)
{
case '-':
case '+':
case '*':
case '/':
return true;
default:
return false;
break;
}
}
//确定操作符的优先级
int Priority(char op)
{
switch (op)
{
case '-':
case '+':
return 1;
case '*':
case '/':
return 2;
default:
break;
}
}
//中缀转后缀
void Transition()
{
stack<int> tmp;
int n = _infix.size();
for (int i = 0; i < n; i++)//1
{
if ((_infix[i] + 48) >= '0' && (_infix[i] + 48) <= '9' || _infix[i] == '.')//2
{
_exp.push_back(Cell({ OP_NUM, _infix[i] }));
}
else if (_infix[i] == '(') //i
{
tmp.push(_infix[i]);
}
else if (_infix[i] == ')') //ii
{
while (tmp.top() != '(')
{
_exp.push_back(Cell({ OP_SYMBOL, tmp.top() }));
tmp.pop();
}
tmp.pop();
}
else if (IsTypeid(_infix[i])) //iii
{
if (tmp.empty() || Priority(_infix[i]) > Priority(tmp.top())) //(1)
{
tmp.push(_infix[i]);
}
else
{
while (!tmp.empty() && Priority(_infix[i]) <= Priority(tmp.top())) //(2)
{
_exp.push_back(Cell({ OP_SYMBOL, tmp.top() }));
tmp.pop();
}
tmp.push(_infix[i]);
}
}
else
{
cout << "输入有误" << endl;
_exp.clear();
return;
}
}
while (!tmp.empty())
{
_exp.push_back(Cell({ OP_SYMBOL, tmp.top() }));
tmp.pop();
}
}
protected:
vector<int> _infix; //中缀
vector<Cell> _exp; // 表达式
};
int TestCalculator()
{
vector<int> exp = { 2, '-', 3, '*', 4, '+', 5 };
Calculator cal(exp);
int n = cal.Count();
cout << "计算结果:" << n << endl;
return 0;
}
方法二:利用 javax.script.ScriptEngine
在 Java 中计算字符串数值表达式可以用 javax.script.ScriptEngine#eval(java.lang.String)
,通过调用 JavaScript
来计算
代码:
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class ExpresionCalculate {
public static void main(String[] args) {
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("nashorn");
String expression1 = " 12 * (3 + 4) - 6 + 8 / 2;";
try {
String result = String.valueOf(scriptEngine.eval(expression1));
System.out.println(result);
} catch (ScriptException e) {
e.printStackTrace();
}
}
}
参考文献:
1:【经典算法】-算术表达式求值
2. Java 中计算字符串表达式的值