1.给出两个数,用户再指定操作符,要求计算结果,这实现起来很容易;
2.多个数,但只涉及同一优先级的操作符,做起来也很容易;
3.多个数,不同优先级的操作符,怎么办呢?
想想就xx,不过还好前人已经为我们留下了很多解决这个问题的方法。通过逆波兰表达式是解决这个问题很流行的一种方式。
一、什么是逆波兰表达式?
我们一般使用的表达式,形如1+2*3,被称为中缀表达式,转换为后缀表达式即为:1 2 3 * +,而后缀表达式也就是我们所说的逆波兰表达式。
逆波兰表达式计算起来非常方便:遇操作数入栈,遇操作符则弹出栈顶两个元素进行计算并将结果推入栈中,直至结束。上面的表达式的计算过程可以用下图表示:
也正因为逆波兰的方便性,使它成为了计算器的普遍实现方式。
二、四则混合运算计算器
既然思路已经清晰了,那么我们的计算器可以分两步走:
1.将表达式转换为逆波兰形式
2.逆波兰表达式求值。
生成逆波兰表达式:
将一般中缀表达式转换为逆波兰表达式有如下转换过程:
(1)首先构造一个运算符栈,此运算符在栈内遵循越往栈顶优先级越高的原则。
(2)读入一个用中缀表示的简单算术表达式,为方便起见,设该简单算术表达式的右端多加上了优 先级{zd1}的特殊符号“#”。
(3)从左至右扫描该算术表达式,从{dy}个字符开始判断,如果该字符是数字,则分析到该数字串 的结束并将该数字串直接输出。
(4)如果不是数字,该字符则是运算符,此时需比较优先关系。
做法如下:将该字符与运算符栈顶的运算符的优先关系相比较。如果,该字符优先关系高于此运算符 栈顶的运算符,则将该运算符入栈。倘若不是的话,则将栈顶的运算符从栈中弹出,直到栈顶运算符的优先级低于当前运算符,将该字符入栈。
(5)重复上述操作(3)-(4)直至扫描完整个简单算术表达式,确定所有字符都得到正确处理,我们便可以将中缀式表示的简单算术表达式转化为逆波兰表示的简单算术表达式。
而上面提到的运算符优先级如下:
下面是程序化算法流程:
1、建立运算符栈stackOperator用于运算符的存储,压入'\0'。
2、预处理表达式,正、负号前加0(如果一个加号(减号)出现在最前面或左括号后面,则该加号 (减号)为正负号) 。
3、顺序扫描表达式,如果当前字符是数字(优先级为0的符号),则直接输出该数字;如果当前字 符为运算符或括号(优先级不为0的符号),则判断第4点 。
4、若当前运算符为'(',直接入栈;
若为')',出栈并顺序输出运算符直到遇到{dy}个'(',遇到的{dy}个'('出栈但不输出;
若为其它,比较stackOperator栈顶元素与当前元素的优先级:
如果 栈顶元素 >= 当前元素,出栈并顺序输出运算符直到 栈顶元素 < 当前元素,然后当前元素入栈;
如果 栈顶元素 < 当前元素,直接入栈。
5、重复第3点直到表达式扫描完毕。
6、顺序出栈并输出运算符直到栈顶元素为'\0'。
a.预处理表达式,对于一元+,-在前面加0:
/// 处理正负号
/// </summary>
/// <param name="exp"></param>
/// <returns></returns>
private static string FormatExp(string exp)
{
var result = exp.Trim().Replace(" ", "");
if (result[0] == '+' || result[0] == '-')
{
result = "0" + result;
}
for (var i = 0; i < result.Length - 1; i++)
{
if (result[i] == '(' && (result[i + 1] == '+' || result[i + 1] == '-'))
{
result = result.Substring(0, i + 1) + "0" + result.Substring(i + 1);
}
}
return result;
}
b.将格式化后的表达式转换为逆波兰表达式
/// 将解析过后的表达式转换为逆波兰表达式(各个语义单元存放在列表中)
/// </summary>
/// <param name="tokens"></param>
/// <returns></returns>
private static List<string> ToRPNExp(List<string> tokens)
{
var result = new List<string>();
var opStack = new Stack<string>();
opStack.Push("\0");
for (var i = 0; i < tokens.Count; i++)
{
if (IsOperator(tokens[i]))
result.AddRange(HandleOperator(opStack, tokens[i]));
else result.Add(tokens[i]);
}
while (opStack.Peek() != "\0")
{
result.Add(opStack.Pop());
}
return result;
}
/// <summary>
/// 根据操作符的优先级决定入栈还是出栈
/// </summary>
/// <param name="st"></param>
/// <param name="op"></param>
/// <returns></returns>
private static List<string> HandleOperator(Stack<string> st, string op)
{
var result = new List<string>();
if (op == "(")
{
st.Push(op);
}
else if (op == ")")
{
while (st.Peek() != "(")
{
result.Add(st.Pop());
}
st.Pop();
}
else
{
var priority1 = IcpPriority[op];
var priority2 = IspPriority[st.Peek()];
while (priority2 >= priority1)
{
result.Add(st.Pop());
priority2 = IspPriority[st.Peek()];
}
st.Push(op);
}
return result;
}
求值:
/// 进行计算
/// </summary>
/// <param name="tokens"></param>
/// <returns></returns>
private static double DoCalc(List<string> tokens)
{
var st = new Stack<double>();
foreach (var token in tokens)
{
if (!IsOperator(token))
{
//操作数入栈
st.Push(double.Parse(token));
}
else
{
//操作符从栈顶取出两个元素进行计算,并将结果推入栈中
var operand1 = st.Pop();
var operand2 = st.Pop();
switch (token)
{
case "+":
st.Push(operand2 + operand1);
break;
case "-":
st.Push(operand2 - operand1);
break;
case "*":
st.Push(operand2 * operand1);
break;
case "/":
st.Push(operand2 / operand1);
break;
case "^":
st.Push(Math.Pow(operand2, operand1));
break;
}
}
}
//最终栈顶元素即为表达式的值
return st.Pop();
}
就这样,一个完整的计算器就完成了,不会的朋友现在也会了吧?
【参考资料】