在讲述算法之前,我们需要先学习几个概念。

中缀表示法

中缀表示法就是我们人书写表达式的方法,如8/4+3*(6-2)。

后缀表示法

后缀表示法是从中缀表示法转化过来的,它满足以下条件:

(1)操作数的顺序与中缀表达式一致。

(2)没有括号。

(3)操作符没有优先级之分。

例如上面的表达式,其后缀形式是:84/362-*+

后缀表达式的特点对计算机计算非常有利。

二元运算符

需要两个操作数的运算符,例如是加法、减法、乘法、乘方、大于、等于等。

一元运算符

只需要一个操作数的运算符,例如是负数、逻辑非、正弦、平方根等。

我们下面介绍的算法,将支持以下运算:

  数值运算 逻辑运算
二元 + - * / %(求余) ^(乘方) and or & |
  e(科学计数法) < > = != <= >=
一元 + - !
  sin cos tan asin acos atan  
  sqrt lg  

利用这些运算,如果希望增加其他运算,会是很容易的。

一、表达式预处理

预处理包括去除空白字符、转成小写、判断括号是否匹配,以及加入结束符。

private static string PreTreat(string exp)
{if (string.IsNullOrEmpty(exp)){throw new Exception("表达式为空");}exp = exp.Replace(" ", "").Replace("\t", "").Replace("\r", "").Replace("\n", "");if (string.IsNullOrEmpty(exp)){throw new Exception("表达式为空");}if (!IsBracketMatch(exp)){throw new Exception("左右括号不匹配");}exp = exp.ToLower();exp = exp + "@";return exp;
}

结束符的作用是不需要每次扫描都判断是否越界。假如我们的表达式是8.1*sqrt(4)/-4+3*(16-23),经过预处理后,就变成了8.1*sqrt(4)/-4+3*(16-23)@。

二、元素切割

我们的表达式是一个字符串,由一个一个的字符组成,字符与字符之间并没有联系。我们需要把上面的字符串,变成以下的一个操作数和运算符分隔的列表:8.1,*,sqrt,(,4,),/,-,4,+,3,(,16,-,23,),@

切割的代码如下:

private static List<string> Segment(string exp)
{List<string> segments = new List<string>();int current = 0;int pre_type = 1;//上一个类型,1符号,2字母,3数字while (current < exp.Length){if (SPLIT_SYMBOL.Contains(exp[current])){segments.Add(exp[current].ToString());switch (exp[current]){case '<':switch (exp[current + 1]){case '>':case '=':segments[segments.Count - 1] += exp[current + 1];current++;break;}break;case '>':case '!':switch (exp[current + 1]){case '=':segments[segments.Count - 1] += exp[current + 1];current++;break;}break;}pre_type = 1;}else{int type = 3;if (LETTER.IsMatch(exp[current].ToString())){type = 2;}if (pre_type != type){segments.Add(exp[current].ToString());}else{segments[segments.Count - 1] += exp[current];}pre_type = type;}current++;}return segments;
}

三、转成后缀表示法

转换的方法如下:

(1)初始化一个堆栈和一个队列,堆栈存放中间过程,队列存放结果。(结果也可以用堆栈存放,不过要反过来装载一遍)
(2)从左到右读入中缀表达式,每次一个元素。
(3)如果元素是操作数,将它添加到结果队列。
(4)如果元素是个运算符,弹出运算符,直至遇见左括号、优先级较低的运算符。把这个运算符压入堆栈。
(5)如果元素是个左括号,把它压入堆栈。
(6)如果元素是个右括号,在遇见左括号前,弹出所有运算符,然后把它们添加到结果队列。
(7)如果到达输入字符串的末尾,弹出所有运算符并添加到结果队列。

代码如下:

private static Stack<object> Parse(List<string> segments)
{Stack<object> syntax = new Stack<object>();//语法单元堆栈Stack<object> operands = new Stack<object>();//操作数堆栈Stack<Operator> operators = new Stack<Operator>();//运算符堆栈bool pre_opt = true;//前一个符号是运算符for (int i = 0; i < segments.Count; i++){string ele = segments[i];if (ALL_OPERATOR.Contains(ele))//运算符{Operator opt = new Operator(ele, !pre_opt);pre_opt = true;//若当前运算符为结束运算符,则停止循环if (opt.Type == OperatorType.END){break;}//若当前运算符为左括号,则直接存入堆栈。if (opt.Type == OperatorType.LB){operators.Push(new Operator(OperatorType.LB));continue;}//若当前运算符为右括号,则依次弹出运算符堆栈中的运算符并存入到操作数堆栈,直到遇到左括号为止,此时抛弃该左括号.if (opt.Type == OperatorType.RB){while (operators.Count > 0){if (operators.Peek().Type != OperatorType.LB){operands.Push(operators.Pop());}else{operators.Pop();break;}}pre_opt = false;continue;}//若运算符堆栈为空,或者若运算符堆栈栈顶为左括号,则将当前运算符直接存入运算符堆栈.if (operators.Count == 0 || operators.Peek().Type == OperatorType.LB){operators.Push(opt);continue;}//若当前运算符优先级大于运算符栈顶的运算符,则将当前运算符直接存入运算符堆栈.if (opt.CompareTo(operators.Peek()) > 0){operators.Push(opt);}else{//若当前运算符若比运算符堆栈栈顶的运算符优先级低或相等,则输出栈顶运算符到操作数堆栈,直至运算符栈栈顶运算符低于(不包括等于)该运算符优先级,//或运算符栈栈顶运算符为左括号//并将当前运算符压入运算符堆栈。while (operators.Count > 0){if (opt.CompareTo(operators.Peek()) <= 0 && operators.Peek().Type != OperatorType.LB){operands.Push(operators.Pop());if (operators.Count == 0){operators.Push(opt);break;}}else{operators.Push(opt);break;}}}}else//操作数{operands.Push(ele);pre_opt = false;}}//转换完成,若运算符堆栈中尚有运算符时,//则依序取出运算符到操作数堆栈,直到运算符堆栈为空while (operators.Count > 0){operands.Push(operators.Pop());}//调整操作数栈中对象的顺序并输出到最终栈while (operands.Count > 0){syntax.Push(operands.Pop());}return syntax;
}

对于+、-符号的判断,我们采用以下方法:

(1)如果符号前是非右括号的运算符,则判定为正号、负号。

(2)除此,判定为加号、减号。

经过这一步骤后,上述的表达式转成如下的后缀表达式:

8.1,4,sqrt,*,4,-(负号),/,3,16,23,-,*,+

四、对后缀表达式进行计算

后缀表达式的计算还是比较简单的,方法如下:

(1)初始化一个空堆栈。
(2)从左到右读入后缀表达式。
(3)如果字符是一个操作数,把它压入堆栈。
(4)如果字符是个运算符,弹出两个操作数,执行恰当操作,然后把结果压入堆栈。
(5)到后缀表达式末尾,从堆栈中弹出结果。

实现代码:

private static string Evaluate(Stack<object> syntax)
{if (syntax.Count == 0){return null;}Stack<string> opds = new Stack<string>();Stack<object> pars = new Stack<object>();string opdA, opdB;double numA = 0, numB = 0;string strA = "", strB = "";foreach (object item in syntax){if (item is string){opds.Push((string)item);}else{switch (((Operator)item).Type){//二元case OperatorType.POW://乘方case OperatorType.SCI://科学记数法case OperatorType.FIX://定点case OperatorType.MUL://乘case OperatorType.DIV://除case OperatorType.MOD://余case OperatorType.ADD://加case OperatorType.SUB://减case OperatorType.LT://小于case OperatorType.GT://大于case OperatorType.LE://小于等于case OperatorType.GE://大于等于opdA = opds.Pop();opdB = opds.Pop();if (!double.TryParse(opdA.ToString(), out numA) || !double.TryParse(opdB.ToString(), out numB)){throw new Exception("操作数必须均为数字");}break;case OperatorType.AND://与case OperatorType.OR://或opdA = opds.Pop();opdB = opds.Pop();strA = opdA.ToString().ToLower();strB = opdB.ToString().ToLower();if (strA == "1" || strA == "true"){strA = "1";}else if (strA == "0" || strA == "false"){strA = "0";}else{throw new Exception("操作数必须均为逻辑类型");}if (strB == "1" || strB == "true"){strB = "1";}else if (strB == "0" || strB == "false"){strB = "0";}else{throw new Exception("操作数必须均为逻辑类型");}break;case OperatorType.ET://等于case OperatorType.UT://不等于opdA = opds.Pop();opdB = opds.Pop();strA = opdA.ToString().ToLower();strB = opdB.ToString().ToLower();if (strA == "1" || strA == "true"){strA = "1";}else if (strA == "0" || strA == "false"){strA = "0";}if (strB == "1" || strB == "true"){strB = "1";}else if (strB == "0" || strB == "false"){strB = "0";}break;//一元case OperatorType.PS://正case OperatorType.NS://负case OperatorType.TAN://正切case OperatorType.ATAN://反正切case OperatorType.SIN://正弦case OperatorType.ASIN://反正弦case OperatorType.COS://余弦case OperatorType.ACOS://反余弦case OperatorType.SQRT://开平方case OperatorType.LG://对数opdA = opds.Pop();if (!double.TryParse(opdA.ToString(), out numA)){throw new Exception("操作数必须为数字");}break;case OperatorType.NOT://非opdA = opds.Pop();strA = opdA.ToString().ToLower();if (strA == "1" || strA == "true"){strA = "1";}else if (strA == "0" || strA == "false"){strA = "0";}else{throw new Exception("操作数必须为逻辑类型");}break;}switch (((Operator)item).Type){//二元case OperatorType.POW://乘方opds.Push(Math.Pow(numB, numA).ToString());break;case OperatorType.SCI://科学记数法opds.Push((numB * Math.Pow(10, numA)).ToString());break;case OperatorType.FIX://定点opds.Push(numB.ToString("F" + numA));break;case OperatorType.MUL://乘opds.Push((numB * numA).ToString());break;case OperatorType.DIV://除opds.Push((numB / numA).ToString());break;case OperatorType.MOD://余opds.Push((numB % numA).ToString());break;case OperatorType.ADD://加opds.Push((numB + numA).ToString());break;case OperatorType.SUB://减opds.Push((numB - numA).ToString());break;case OperatorType.AND://与if (strA == "1" && strB == "1"){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.OR://或if (strA == "1" || strB == "1"){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.ET://等于if (strA == strB){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.UT://不等于if (strA != strB){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.LT://小于if (numB < numA){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.GT://大于if (numB > numA){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.LE://小于等于if (numB <= numA){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.GE://大于等于if (numB >= numA){opds.Push("1");}else{opds.Push("0");}break;//一元case OperatorType.PS://正opds.Push(numA.ToString());break;case OperatorType.NS://负opds.Push((-numA).ToString());break;case OperatorType.TAN://正切opds.Push(Math.Tan(numA).ToString());break;case OperatorType.ATAN://反正切opds.Push(Math.Atan(numA).ToString());break;case OperatorType.SIN://正弦opds.Push(Math.Sin(numA).ToString());break;case OperatorType.ASIN://反正弦opds.Push(Math.Asin(numA).ToString());break;case OperatorType.COS://余弦opds.Push(Math.Cos(numA).ToString());break;case OperatorType.ACOS://反余弦opds.Push(Math.Acos(numA).ToString());break;case OperatorType.SQRT://开平方opds.Push(Math.Sqrt(numA).ToString());break;case OperatorType.LG://对数opds.Push(Math.Log10(numA).ToString());break;case OperatorType.NOT://非if (strA == "1"){opds.Push("0");}else{opds.Push("1");}break;}}}if (opds.Count == 1){return opds.Pop();}return null;
}

至此,表达式的求值完成。

C#表达式求值算法(干货)相关推荐

  1. 《Algorithms》—— Dijkstra 的双栈算术表达式求值算法

    想当年学数据结构的时候,一直觉得这个是我一辈子都搞不懂的一个东西.现在看看...还挺简单的... 重点在于如何解析由括号.运算符和数字组成的字符串,并按照正确的顺序完成各种初级算术操作.利用了两个栈( ...

  2. 北京林业大学数据结构实验二 基于栈的算术表达式求值算法

    第1关:基于栈的中缀算术表达式求值 参见课本P75 例3.3 #include <iostream> #include<iomanip>#define MAXSIZE 100 ...

  3. 编程题实训-实验2-基于栈的算术表达式求值算法(北京林业大学)

    第1关:基于栈的中缀算术表达式求值 任务描述 本关任务:输入一个中缀算术表达式,求解表达式的值.运算符包括+.-.*./.(.).=,参加运算的数为double类型且为正数.(要求:直接针对中缀算术表 ...

  4. 表达式求值及转换算法

    2019独角兽企业重金招聘Python工程师标准>>> 后缀表达式求值算法 stack operands; //运算数栈 while(没到表达式尾) {scanf("一个运 ...

  5. 数据结构—— 基于二叉树的算术表达式求值

    实验五 基于二叉树的算术表达式求值 数据结构--中序表达式求值(栈实现) 实验目的: 1.掌握二叉树的二叉链表存储表示和二叉树的遍历等基本算法. 2.掌握根据中缀表达式创建表达式树的算法 3.掌握基于 ...

  6. 【数据结构】栈的应用-算术表达式求值#数据结构实验任务书

    实验题目:栈的应用-算术表达式求值 正文 实验环境: Visual C++ 2010 实验目的: 1.掌握栈的定义及实现: 2.掌握利用栈求解算术表达式的方法. 实验内容: 通过修改完善教材中的算法3 ...

  7. 简单计算器(浙大复试题)表达式求值

    读入一个只包含 +.-.*./ 的非负整数计算表达式,计算该表达式的值. 输入 测试输入包含若干测试用例,每个测试用例占一行,每行不超过200个字符,整数和运算符之间用一个空格分隔.没有非法表达式.当 ...

  8. 栈的应用-算数表达式求值

    List item ** 实验要求** 河 南 师 范 大 学 20学年-21学年第 1 学期 数据结构实验任务书 专业名称: 实验学时: 4 课程名称:数据结构 任课教师: 王亚丽 实验题目:栈的应 ...

  9. 栈实现算术表达式求值

    算术表达式求值 利用栈求解的一个典型的问题是算术表达式求值,例如:"3+4*2-(1+1)#",这样的表达式计算,在计算过程中,不是读到一个运算就立即计算,而是要与后面的运算符进行 ...

最新文章

  1. 34 多线程同步之Event
  2. C# DataTable转ListModel通用类
  3. C++实现线程安全的单例模式
  4. 【c基础】之 文件及其操作
  5. VMware8.0虚拟机中安装Ubuntu12.04使用NAT设置连接网络
  6. dj鲜生-35-设置django的session使用redis来存储
  7. Java 导出 Excel 文件
  8. Android程序的退出
  9. 计数器:counter
  10. 6Y叔的clusterProfiler-book阅读 Chapter 6 KEGG analysis
  11. android接口类命名规范_Android开发规范
  12. 阶段3 2.Spring_10.Spring中事务控制_10spring编程式事务控制2-了解
  13. uml通信图画法_UML各种图画法总结
  14. 移动端后台管理系统框架
  15. [嵌入式linux]RTL8111/RTL8168网卡内核驱动安装
  16. linux基础教程-黑马程序员汇总PDF
  17. 光线追踪技术 第二章
  18. 【高等数学】微积分----教你如何简单地推导求导公式(一)
  19. linux 命令详解 大于号_Linux 命令出现号(大于号)如何退出[组图]
  20. mysql:列类型之Spatial

热门文章

  1. lighttpd配置介绍
  2. 路由器无线中继设置教程
  3. 猎聘品牌升级李易峰为代言人 官网启用双拼域名liepin.com
  4. C#云之家审批流程示例
  5. OpenGL模型加载之模型
  6. win11怎么更改图片格式
  7. Javascript方法实现拖曳和用HTML实现拖曳
  8. 时间序列预测(1)-什么是时间序列预测
  9. Hello Git(五)——Git分布式工作流程
  10. 聚合支付、单商户多商户支付、微信/支付宝/PayPal支付流程、支付政策法规