中缀表达式转后缀表达式,实现简单计算器功能(C#编写)
写在前面:最开始了解中缀表达式和后缀表达式是在leetcode刷题刷到的。当时其实并没有深入想过这个有什么用,只是单纯的把题做出来而已。后来读了吴军的《计算之魂》里面P82提到了:“在很多大学计算机系的编程课或数据结构课中,初学者会被要求使用堆栈实现一个简单的计算器“。并大概在书中讲解了一下堆栈在这里的使用。然后我觉得这个地方好像似曾相识,就上网搜了一下。网上有很多讲解中缀表达式转后缀表达式的文章,这里只是记录一下自己的学习过程和体会。
在了解后缀表达式之前,我有用C#实现过一个复杂的计算器。之所以说复杂,是它还能算sin,cos这类三角函数。当时是在弄编译器相关的东西,然后发现了Nuget上一个叫做Irony的库。我感觉Irony就是一个简化的C#版本的lex&yacc,只要给定语法规则,就能够把算术表达式解析为一颗语法树。得到语法树之后,实现计算功能就很容易了。后面如果有心情,加之如果工作需要,考虑把使用Irony实现计算器的代码贴上来。
中缀表达式就是我们日常用的算术表达式的表示方法,例如:3+4*7*(8-3*2-1)-2。但是这种表达式不容易被计算机解析,如果全是加法和减法还好,它俩同属一个优先级,只要依次按顺序计算就行了。但是涉及到高优先级的乘法、除法和括号,就不能按顺序计算了。比如前面那个算术表达式,需要先计算3*2。因此,可以理解为,前缀表达式和后缀表达式本质上就是把中缀表达式转换成一种能够顺序计算的表达式。前面那个算数表达式转换成后缀表达式以及运算过程如下图所示。可见,数字的位置是不变的,变得只是符号的位置。
可见,实现简单的计算器功能可以分两步走:
1.中缀表达式转成后缀表达式
2.计算后缀表达式
首先来看第一步。这一步需要用到一个列表或者数组记录后缀表达式中的每一个元素,以及一个栈结构记录运算符号。依次读字符串中的每个字符:
- 遇到乘号和除号,直接Push进栈;
- 遇到加号和减号,依次弹出栈顶元素,直到遇到左括号,或者栈空。但我觉得这里有个很恶心的东西,“-”号带来的一元表达式。
- 遇到左括号,Push进栈;
- 遇到右括号,弹出栈顶元素直到遇到左括号。
代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
private static List<string> postfixExpression(string expression) { // 列表用于存储后缀表达式中的元素 List<string> elements = new List<string>(); // 栈用于存储符号,也就是+,-,*,/,(,) Stack<char> operators = new Stack<char>(); // 记录数字的字符串 string num = String.Empty; for (int i = 0; i < expression.Length; i++) { char currentChar = expression[i]; // 如果当前字符是数字,则添加到num中 if (Char.IsDigit(currentChar) || currentChar=='.') num += currentChar; else if(currentChar=='*' || currentChar == '/') // 如果当前字符是乘号或除号 { if(num!=String.Empty) // num不为空表示乘号或除号前面有数字,把num添加到nums中 { elements.Add(num); num = String.Empty; } operators.Push(currentChar); // 乘号和除号的优先级高,可以直接push进栈 } else if(currentChar=='+' || currentChar == '-') // 当前字符是加号或减号 { if (operators.Count == 0) { if (num != String.Empty) { elements.Add(num); num = String.Empty; } else { if (currentChar == '-' && i != 0 && expression[i - 1] != ')') elements.Add("0"); else if (currentChar == '-' && i == 0) elements.Add("0"); } } else { Char lastOperator = operators.Peek(); if (lastOperator == '(') { if (num == String.Empty) { if(currentChar=='-') elements.Add("0"); } else { elements.Add(num); num = String.Empty; } } else { if (num != String.Empty) { elements.Add(num); num = String.Empty; } while (operators.Count != 0) { lastOperator = operators.Pop(); if (lastOperator != '(') elements.Add(lastOperator.ToString()); else { operators.Push(lastOperator); break; } } } } operators.Push(currentChar); } else if (currentChar == '(') // 左括号直接push进栈 { operators.Push(currentChar); } else if (currentChar == ')') // 右括号的情况下,弹出栈顶元素添加到nums中,直到遇到左括号 { if(num!= String.Empty) { elements.Add(num); num = String.Empty; } char lastOperator = operators.Pop(); while (lastOperator != '(') { elements.Add(lastOperator.ToString()); lastOperator = operators.Pop(); } } } if (num != String.Empty) elements.Add(num); // 如果表达式最后是数字,别忘了还得把它添加到nums中 while (operators.Count != 0) // 把栈内剩下的符号依次弹出,并添加到nums中 { elements.Add(operators.Pop().ToString()); } return elements; }
测试一下,对于上面给出的算术表达式,输出的后缀表达式为:
第二步相对简单很多了,这里直接贴出代码了:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
private static double calculate(List<string> nums) { Stack<double> _params = new Stack<double>(); foreach(var item in nums) { if(item=="+" || item=="-" || item=="*" || item == "/") { double p1 = _params.Pop(); double p2 = _params.Pop(); switch (item) { case "+": _params.Push(p1 + p2); break; case "-": _params.Push(p2 - p1); break; case "*": _params.Push(p1 * p2); break; case "/": _params.Push(p2 / p1); break; } } else { _params.Push(Double.Parse(item)); } } return _params.Pop(); }
我随便运行了几个例子,都能得到正确的答案。但是我不能保证代码完全没错误。