前言:四则运算,大家都不陌生,在上小学的时候,数学中学到过的知识,那么如何在程序中实现呢?下面,我们就用程序来实现9+(3-2)*(5-3)/4*3,这个算式的值。计算的时候,有一个规则”先乘除,后加减,从左到右,先括号内后括号外“。
其优先级就是 加减<乘除<括号
这个算式,我们可以很轻松的计算出它的值等于10.5。这是我们常用的四则表达式,又叫做中缀表达式。这种计算的复杂之处在与乘除在加减之后,并且还有括号,放在程序里的判断,就复杂了,那么如何简化呢?伟大的科学家想到了好的处理办法,
一、逆波兰(Reverse Polish Notation,RPN):一种不需要括号的后缀表达法,我们也称之为逆波兰。
上面的四则运算表达式,转换未后缀表达法之后,变为 9 3 2 - * 5 3 - 4 / 3 * +,叫后缀的原因所有的符号都是在要运算数字的后面出现。
如何实现由中缀表达式转化未后缀表达式呢?
规则:依次从左向右遍历表达式,若是数字加入到集合;若是符号,则需要判断其与栈顶符号的优先级,如果当前元素是右括号或优先级较低的符号,则栈顶的元素一次出栈并输出,并将当前符号进栈;如果当前元素和栈顶的元素优先级相同,将栈顶的同级元素依次出栈。
实现代码:
/// <summary>
/// 利用正则表达式,分别获取数字和符号,然后组装到集合中。
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private List<string> GetOpList(string str)
{string pattern = (@"\d+");Regex validate = new Regex(pattern);MatchCollection col = validate.Matches(str);//string patternFh = (@"(\+|\-|\*|\/|\(|\))");string patternFh = @"\D+";Regex validateFh = new Regex(patternFh);MatchCollection colFh = validateFh.Matches(str);List<string> list = new List<string>();int nFhIndex = 0;for (int i = 0; i < col.Count; i++){Match mD = col[i];list.Add(mD.Value);if (nFhIndex < colFh.Count){Match mf = colFh[nFhIndex];string strTemp = mf.Value;int nTempLen = strTemp.Length;for(int j = 0; j < nTempLen; j++){list.Add(strTemp.Substring(j, 1));}nFhIndex++;}}return list;
}/// <summary>
/// 当前只限定加减乘除小括号
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private List<string> GetNextPrevRegex(string str)
{string hig = "*/"; //优先级高的string zkh = "("; //左括号string ykh = ")"; //右括号Stack stack = new Stack();List<string> list = new List<string>();List<string> listOp = GetOpList(str);//从左到右遍历中缀表达式的每个数字和符号,for (int i = 0; i < listOp.Count; i++){string item = listOp[i];//1、若是数字输出,即成为后缀表达式的一部分;if (IsNumeric(item)){list.Add(item);continue;}if (stack.Count == 0 || zkh == item){stack.Push(item);continue;}//这里限定了默认条件,是必须+-*/),才到这里。while (stack.Count > 0){string ab = stack.Peek().ToString();if (ykh == item){if (zkh == ab){stack.Pop(); //将左括号出栈break;}list.Add(stack.Pop().ToString());continue;}if (!hig.Contains(ab))break;list.Add(stack.Pop().ToString());}if (!ykh.Contains(item))stack.Push(item);}while (stack.Count > 0){list.Add(stack.Pop().ToString());}return list;
}private bool IsNumeric(string token)
{bool blag = true;string pattern = (@"^\d+$");Regex validate = new Regex(pattern);if (!validate.IsMatch(token)){blag = false;}return blag;
}
二、由上面计算得到后缀表达式,那么如何使用栈来计算这个表达式呢?
计算规则:依次遍历后缀表达式,如果是数字,则入栈,如果是符号,则将栈顶的两个元素出栈,进行计算,计算完成之后,将结果入栈。
代码如下:
public double GetResult(string calc)
{List<string> list = GetNextPrevRegex(calc);Stack calcStack = new Stack();foreach (var item in list){string temp = item.ToString();//如果是数字,入栈if (IsNumeric(temp)){calcStack.Push(temp);continue;}//如果是符号,则把栈顶的两个数字出栈,进行计算,入栈。double.TryParse(calcStack.Pop().ToString(), out double hz2);double.TryParse(calcStack.Pop().ToString(), out double hz1);double result = GetResultByFh(hz1, hz2, temp);calcStack.Push(result);}string sResult = calcStack.Pop().ToString();double.TryParse(sResult, out double calcresult);return calcresult;
}
private double GetResultByFh(double ca1, double ca2, string fh)
{if (fh == "+")return ca1 + ca2;else if (fh == "-")return ca1 - ca2;else if (fh == "*")return ca1 * ca2;elsereturn ca1 / ca2;
}
备注:其中逆波兰解释,来自《大话数据结构》一书。
欢迎大家批评指正,小可不胜感激。