运用大众例子:
所谓的逆波兰表示法(Reverse Polish notation,RPN,或逆波兰记法),是一种数学表达式方式,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。(摘自维基)
我们平时长写的数学公式被称为中缀表达式,即:(a+b)*c,用逆波兰式为ab+c*,再如:(a+b)*c-(a+b)/e,逆波兰式为ab+c*ab+e/-,首先理解下转化成逆波兰式的几个步骤:
算法实现(选自百度):
首先需要分配2个栈,一个作为临时存储运算符的栈S1(含一个结束符号),一个作为输入逆波兰式的栈S2(空栈),S1栈可先放入优先级最低的运算符#,注意,中缀式应以此最低优先级的运算符结束。可指定其他字符,不一定非#不可。从中缀式的左端开始取字符,逐序进行如下步骤:
static void Main(string[] args) { while (true) { Console.WriteLine("输入计算公式:"); //输入公式 string formula = Console.ReadLine(); //定义临时计算符栈 Stack<string> opStack = new Stack<string>(); opStack.Push("#"); Stack<string> numStack = new Stack<string>(); for (int i = 0; i < formula.Length;) { int opNum = GetOperationLevel(formula[i].ToString()); if (opNum == 0) { int index = GetCompleteValue(formula.Substring(i, formula.Length - i)); numStack.Push(formula.Substring(i, index)); i = (i + index); }//为操作数,获取完整 else { if (formula[i] == '(') { opStack.Push(formula[i].ToString()); } else if (formula[i] == ')') { MoveOperator(opStack, numStack); } else { if (opStack.Peek() == "(") { opStack.Push(formula[i].ToString()); } else { JudgeOperator(opStack, numStack, formula[i].ToString()); } } i++; } } if (opStack.Count != 0) { while (opStack.Count != 0 && opStack.Peek() != "#") { numStack.Push(opStack.Pop()); } } StringBuilder strBuild = new StringBuilder(); foreach (string s in numStack) { strBuild.Insert(0,s); } Console.WriteLine(strBuild.ToString()); } }
获取运算符等级:
/// <summary> /// 获取运算符等级 /// </summary> /// <param name="c">当前字符</param> /// <returns></returns> private static int GetOperationLevel(string c) { switch (c) { case "+": return 1; case "-": return 1; case "*": return 2; case "/": return 2; case "#": return -1; case "(": return -1; case ")": return -1; default: return 0; } }
获取完整操作数
/// <summary> /// 获取完整数值 /// </summary> /// <param name="formula">公式</param> /// <returns></returns> private static int GetCompleteValue(string formula) { int index = formula.Length; for (int i = 0; i < formula.Length; i++) { int num = GetOperationLevel(formula[i].ToString()); if (num != 0) { index = i; break; } } return index; }
移动运算符:
/// <summary> /// 移动运算符 /// </summary> /// <param name="opStack"></param> /// <param name="numStack"></param> private static void MoveOperator(Stack<string> opStack, Stack<string> numStack) { string s = opStack.Pop(); if (s == "(") { return; } else { numStack.Push(s); MoveOperator(opStack, numStack); return; } }
判断运算符:
/// <summary> /// 判断运算符 /// </summary> /// <param name="opStack"></param> /// <param name="numStack"></param> /// <param name="x"></param> private static void JudgeOperator(Stack<string> opStack, Stack<string> numStack, string x) { int xNum = GetOperationLevel(x); int opNum = GetOperationLevel(opStack.Peek()); if (xNum > opNum || numStack.Peek() == "(") { opStack.Push(x); return; } else { string opStr = opStack.Pop(); numStack.Push(opStr); JudgeOperator(opStack, numStack, x); return; } }
代码运行效果:
接下来就是计算公式的值,思路是新建一个栈R用来放结果,依次取出表达式的栈F,
1.如果取出的值为操作数,则放入栈R,
2.如果取出的值为运算符,则取出栈R顶的两个,进行运算,运算结果在存放入栈R
最后R顶的元素,即为计算的值
下面为代码,目前只做了+,-,*,/
//numStack为逆波兰表达式 Stack<string> rpnFormula = new Stack<string>(); foreach (string s in numStack) { rpnFormula.Push(s); } Console.WriteLine(CalcRPNFormula(rpnFormula));=
private static string CalcRPNFormula(Stack<string> rpnFormula) { Stack<string> resultStack = new Stack<string>(); foreach (string s in rpnFormula) { int num = GetOperationLevel(s); if (num == 0) { resultStack.Push(s); } else { CalcResult(resultStack,s); } } return resultStack.Pop(); } private static void CalcResult(Stack<string> resultStack, string operatorStr) { if (resultStack.Count >= 2) { double num2 = Convert.ToDouble(resultStack.Pop()); double num1 = Convert.ToDouble(resultStack.Pop()); if (operatorStr == "+") { resultStack.Push(Convert.ToString(num1 + num2)); } else if (operatorStr == "-") { resultStack.Push(Convert.ToString(num1 - num2)); } else if (operatorStr == "*") { resultStack.Push(Convert.ToString(num1 * num2)); } else if (operatorStr == "/") { resultStack.Push(Convert.ToString(num1 / num2)); } } }
因为适应自己的项目,需要做到支持“”^”,"arctan", "sqrt", "sin", "cos", "tan", "arcsin", "arccos"的计算方式,在查看上面生成的逆波兰式的时候,发现^会后置,其他的都是前置,然后就投机取巧了以下,遇到^取前两位,遇到其他特殊符号,则取后一位,所以增加了以下内容,测试了下例子:2^3+sqrt(9)+sin(45)+1 通过之前的代码转换为后缀表达式为23^sqrt9+sin45+1+(正确应为23^9sqrt+45sin+1+),然后根据上述的进行计算,加了这几个特许计算后,可能与原来的后缀表达式有点背道而驰,没有对特殊字符进行后置处理,但目前为了先实现计算,所以投机取巧了下,后期会进行修改
/// <summary> /// 特殊字符 /// </summary> private static List<string> m_SpecialOp = new List<string>() { "arctan", "sqrt", "sin", "cos", "tan", "arcsin", "arccos" }; /// <summary> /// 获取运算符等级 /// </summary> /// <param name="c">当前字符</param> /// <returns></returns> private static int GetOperationLevel(string c) {//sin(cos(tan(arcsin(arccos(arctan( switch (c) { case "+": return 1; case "-": return 1; case "*": return 2; case "/": return 2; case "^": return 2; case "arctan": return 10; case "sqrt": return 10; case "sin": return 10; case "cos": return 10; case "tan": return 10; case "arcsin": return 10; case "arccos": return 10; case "#": return -1; case "(": return -1; case ")": return -1; default: return 0; } } /// <summary> /// 计算逆波兰式 /// </summary> /// <param name="rpnFormula"></param> /// <returns></returns> private static string CalcRPNFormula(Stack<string> rpnFormula) { Stack<string> resultStack = new Stack<string>(); while (rpnFormula.Count > 0) { string rpnStr = rpnFormula.Pop(); int num = GetOperationLevel(rpnStr); if (num == 0) { resultStack.Push(rpnStr); } else if (num == 10) { SpecialCalc(resultStack, rpnStr, rpnFormula.Pop()); } else { CalcResult(resultStack, rpnStr); } } return resultStack.Pop(); } /// <summary> /// 特殊计算 /// </summary> /// <param name="resultStack"></param> /// <param name="operatorStr"></param> /// <param name="calcValue"></param> private static void SpecialCalc(Stack<string> resultStack, string operatorStr, string calcValue) { if (m_SpecialOp.Contains(operatorStr)) { double num = Convert.ToDouble(calcValue); if (operatorStr == "sqrt") { resultStack.Push(Math.Sqrt(num).ToString("#0.000")); } else if (operatorStr == "sin") { resultStack.Push(Math.Sin(num).ToString("#0.000")); } else if (operatorStr == "cos") { resultStack.Push(Math.Cos(num).ToString("#0.000")); } else if (operatorStr == "tan") { resultStack.Push(Math.Tan(num).ToString("#0.000")); } else if (operatorStr == "arcsin") { resultStack.Push(Math.Asin(num).ToString("#0.000")); } else if (operatorStr == "arccos") { resultStack.Push(Math.Acos(num).ToString("#0.000")); } else if (operatorStr == "arctan") { resultStack.Push(Math.Atan(num).ToString("#0.000")); } } } /// <summary> /// 计算结果 /// </summary> /// <param name="resultStack"></param> /// <param name="operatorStr"></param> private static void CalcResult(Stack<string> resultStack, string operatorStr) { if (resultStack.Count >= 2) { double num2 = Convert.ToDouble(resultStack.Pop()); double num1 = Convert.ToDouble(resultStack.Pop()); if (operatorStr == "+") { resultStack.Push(Convert.ToString(num1 + num2)); } else if (operatorStr == "-") { resultStack.Push(Convert.ToString(num1 - num2)); } else if (operatorStr == "*") { resultStack.Push(Convert.ToString(num1 * num2)); } else if (operatorStr == "/") { resultStack.Push(Convert.ToString(num1 / num2)); } else if (operatorStr == "^") { resultStack.Push(Math.Pow(num1, num2).ToString()); } } }