C# 算术表达式求值(后缀法),看这一篇就够了
一、种类介绍
算术表达式有三种:前缀表达式、中缀表达式和后缀表达式。一般用的是中缀,比如1+1
,前后缀就是把操作符移到前面和后面,下面简单介绍一下这三种表达式。
1、前缀表示法
前缀表示法又叫波兰表示法,他的操作符置于操作数的前面(例:+ 1 2
),是波兰数学家扬·武卡谢维奇1920年代引入的,用于简化命题逻辑。因为我们一般认为操作符是在操作数中间的,所以在日常生活中用的不多,但在计算机科学领域占有一席之地。一般的表示法对计算机来说处理很麻烦,每个符号都要考虑优先级,还有括号这种会打乱优先级的存在,将使计算机花费大量的资源进行解析。而前缀表示法没有优先级的概念,他是按顺序处理的。举个例子:9-2*3
这个式子,计算机需要先分析优先级,先乘后减,找到2*3
,再进行减操作;化成前缀表示法就是:- 9 * 2 3
,计算机可以依次读取,操作符作用于后一个操作数,遇到减就是让9减去后面的数,而跟着9的是乘,也就是说让9减去乘的结果,这对计算机来说很简单,按顺序来就行了。
2、中缀表示法
这也就是我们一般的表示法,他的操作符置于操作数的中间(例:1 + 2
),前面也说过这种方法不容易被计算机解析,但他符合人们的普遍用法,许多编程语言也就用这种方法了。在中缀表示法中括号是必须有的,要不然运算顺序会乱掉。
3、后缀表示法
后缀表示法又叫逆波兰表示法,他的操作符置于操作数的后面(例:1 2 +
),他和前缀表示法都对计算机比较友好,但他很容易用堆栈解析,所以在计算机中用的很多。
他的解释过程一般是:操作数入栈;遇到操作符时,操作数出栈,求值,将结果入栈;当一遍后,栈顶就是表达式的值。因此逆波兰表达式的求值使用堆栈结构很容易实现,且能很快求值。
注意:逆波兰记法并不是简单的波兰表达式的反转。因为对于不满足交换律的操作符,它的操作数写法仍然是常规顺序,如,波兰记法/ 6 3
的逆波兰记法是6 3 /
而不是3 6 /
;数字的数位写法也是常规顺序。
4、表示法间转化
这里介绍一种简单的中缀表达式转化前后缀表达式的方法,比如这个式子:a+b*c-(d+e)
1.按照运算符的优先级对所有的运算单位加括号,式子变成:((a+(b*c))-(d+e))
2.前缀表达式,把运算符号移动到对应的括号前面,式子变成:-(+(a*(bc))+(de)),去掉括号:-+a*bc+de
3.后缀表达式,把运算符号移动到对应的括号后面,式子变成:((a(bc)*)+(de)+)-,去掉括号:abc*+de+-
下面的实例方法与上面的有所不同,因为情况更复杂了,请仔细推敲!
二、实例讲解
1、表达式转换
将以下中缀表达式:
3+4*2/(5-3)^2+3*(4-2)
COS(900-3*10*30)+123.45+30*30-0.45+TAN(0)
转化为后缀表达式(RPN):
3,4,2,*,5,3,-,2,^,/,+,3,4,2,-,*,+
900,3,10,*,30,*,-,COS,123.45,+,30,30,*,+,0.45,-,0,TAN,+,
2、程序效果
3、编码分析
编码工作主要分为以下几块:
- 含有函数的中缀表达式转后缀表达式
- 区分数字、函数名、运算符;
- 将字符串拆分为数组形式,方便入栈;
- 判断运算符、函数的优先级,正确排列顺序;
- 针对后缀表达式进行计算
- 区分数字、函数名、运算符的不同操作;
- 根据运算规则不同进行入栈、出栈和计算;
4、转换流程
- 简单算术表达式的中缀转后缀
- 带有函数名的表达式,中缀转后缀
5、计算流程
- 简单算术后缀表达式计算
- 带有函数名的后缀表达式计算
6、实现代码
/*是否为纯数字。正则表达式实现*/
public static bool isNumber(string tmp)
{
return Regex.IsMatch(tmp, @"[0-9]+[.]{0,1}[0-9]*");
}
/*是否为需拆分的运算符+-*^/() */
public static bool isOp(string tmp)
{
bool bRet = false;
switch (tmp)
{
case "+":
case "-":
case "*":
case "/":
case "^":
case "(":
case ")":
bRet = true;
break;
default:
bRet = false;
break;
}
return bRet;
}
/*是否为一元函数名*/
public static bool isFunc(string tmp)
{
bool bRet = false;
switch (tmp)
{
case "SIN":
case "COS":
case "TAN":
case "SQRT":
bRet = true;
break;
default:
bRet = false;
break;
}
return bRet;
}
/*比较运算符及函数优先级。函数视作运算符进行操作。
返回值:1 表示 大于,-1 表示 小于,0 表示 相等 */
public static int compOper(string op1, string op2)
{
int iRet = 0;
Dictionary<string, int> dic = new Dictionary<string, int>();
dic.Add("+", 1);
dic.Add("-", 1);
dic.Add("*", 2);
dic.Add("/", 2);
dic.Add("^", 3);
dic.Add("SIN", 4);
dic.Add("COS", 4);
dic.Add("TAN", 4);
dic.Add("SQRT", 4);
dic.Add("(", 100);
dic.Add(")", 100);
if (dic[op1] > dic[op2])
iRet = 1;
else if (dic[op1] < dic[op2])
iRet = -1;
else
iRet = 0;
return iRet;
}
/*运算符、函数求值*/
public static double calValue(string op, string val1, string val2)
{
double dRet = 0.0d;
switch (op)
{
case "+":
dRet = double.Parse(val1) + double.Parse(val2);
break;
case "-":
dRet = double.Parse(val1) - double.Parse(val2);
break;
case "*":
dRet = double.Parse(val1) * double.Parse(val2);
break;
case "/":
if (double.Parse(val2) != 0)
dRet = double.Parse(val1) / double.Parse(val2);
else
MessageBox.Show("Error!");
break;
case "^":
dRet = Math.Pow(double.Parse(val1), double.Parse(val2));
break;
default:
break;
}
return dRet;
}
public static double calValue(string op, string val1)
{
double dRet = 0.0d;
switch (op)
{
case "SIN":
dRet = Math.Sin(double.Parse(val1));
break;
case "COS":
dRet = Math.Cos(double.Parse(val1));
break;
case "TAN":
dRet = Math.Tan(double.Parse(val1));
break;
case "SQRT":
if (double.Parse(val1) > 0)
dRet = Math.Sqrt(double.Parse(val1));
else
MessageBox.Show("Error!");
break;
default:
break;
}
return dRet;
}
/*按照=+-*^/()分隔出元素*/
public static string splitFunc(string tmp)
{
string sRet = tmp;
sRet = sRet.Replace("=", "\n=\n");
sRet = sRet.Replace("+", "\n+\n");
sRet = sRet.Replace("-", "\n-\n");
sRet = sRet.Replace("*", "\n*\n");
sRet = sRet.Replace("/", "\n/\n");
sRet = sRet.Replace("^", "\n^\n");
sRet = sRet.Replace("(", "\n(\n");
sRet = sRet.Replace(")", "\n)\n");
return sRet;
}
/*中缀表达式转后缀表达式。tmp为已经添加分隔符的中缀表达式字符串*/
public static string midToRPN(string tmp)
{
string sRet = ""; //返回值
string[] strArr = splitFunc(tmp.ToUpper()).Split('\n'); //字符串数组,存放分隔后的中缀表达式元素
Stack<string> strStk = new Stack<string>(); //栈,用于临时存放运算符和函数名
for (int i = 0; i < strArr.Length; i++)
{
if (string.IsNullOrEmpty(strArr[i])) //分隔后为空的元素剔除
continue;
else if (calString.isNumber(strArr[i])) //纯数字直接入队列
sRet += strArr[i] + ',';
else if (calString.isFunc(strArr[i])) //一元函数名直接入栈
strStk.Push(strArr[i]);
else if (calString.isOp(strArr[i])) //运算符特殊处理
{
if (strStk.Count != 0 && strStk.Peek() == "(" && strArr[i] != ")") //栈不为空,最上层为"(",则运算符直接入栈
{
strStk.Push(strArr[i]);
}
else if (strStk.Count != 0 && strArr[i] == ")") //栈不为空,遇")"则pop至"("为止
{
while (strStk.Peek() != "(")
sRet += strStk.Pop() + ',';
strStk.Pop();
if (strStk.Count != 0 && calString.isFunc(strStk.Peek())) //若"("后为一元函数名,则函数名也pop出
sRet += strStk.Pop() + ',';
}
else if (strStk.Count != 0 && calString.compOper(strArr[i], strStk.Peek()) == -1)
{ //栈不为空,运算符优先级小
while (strStk.Count != 0 && strStk.Peek() != "(" && calString.compOper(strArr[i], strStk.Peek()) == -1)
sRet += strStk.Pop() + ','; //则一直pop【存疑】
if (strStk.Count != 0) //pop至优先级不小于栈顶运算符则交换位置
sRet += strStk.Pop() + ','; //先pop
strStk.Push(strArr[i]); //再push
}
else if (strStk.Count != 0 && calString.compOper(strArr[i], strStk.Peek()) == 0)
{ //运算符优先级相同,先pop再push
sRet += strStk.Pop() + ',';
strStk.Push(strArr[i]);
}
else if (strStk.Count != 0 && calString.compOper(strArr[i], strStk.Peek()) == 1)
{ //运算符优先级大,直接入栈
strStk.Push(strArr[i]);
}
else //其他情况,入栈【存疑】
{
strStk.Push(strArr[i]);
}
}
}
while (strStk.Count > 0) //最后栈内元素全部pop出
{
sRet += strStk.Pop() + ',';
}
return sRet; //返回后缀表达式
}
/*根据传入的后缀表达式,求值。tmp为含逗号分隔符的后缀表达式 */
public static double calRPN(string tmp)
{
double dRet = 0.0d;
string[] strArr = tmp.Split(',');
Stack<string> strStk = new Stack<string>();
for (int i = 0; i < strArr.Length - 1; i++)
{
if (isNumber(strArr[i])) //纯数字入栈
strStk.Push(strArr[i]);
else if (isOp(strArr[i])) //二元运算符,pop两个元素,计算值后压入栈
strStk.Push(calValue(strStk.Pop(), strStk.Pop(), strArr[i]).ToString());
else if (isFunc(strArr[i])) //一元函数名,pop一个元素,计算后压入栈
strStk.Push(calValue(strArr[i], strStk.Pop()).ToString());
}
dRet = double.Parse(strStk.Pop()); //取最后栈中元素作为结果值
return dRet;
}
/*调用部分代码*/
private void btnTrans_Click(object sender, EventArgs e)
{
//中缀表达式转后缀表达式
this.tbRPN.Text = calString.midToRPN(this.tbOrigin.Text);
}
private void btnCal_Click(object sender, EventArgs e)
{
//后缀表达式求值
double tmp;
try
{
tmp = calString.calRPN(this.tbRPN.Text);
this.tbRes.Text = tmp.ToString();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
this.tbRes.Text = "Error";
}
finally
{
}
}
C# 算术表达式求值(后缀法),看这一篇就够了的更多相关文章
- 栈的一个实例——Dijkstra的双栈算术表达式求值法
Dijkstra的双栈算术表达式求值法,即是计算算术表达式的值,如表达式(1 + ( (2+3) * (4*5) ) ). 该方法是 使用两个栈分别存储算术表达式的运算符与操作数 忽略左括号 遇到右括 ...
- 【算法】E.W.Dijkstra算术表达式求值
算术表达式求值 我们要学习的一个栈的用例同时也是展示泛型的应用的一个经典例子,就是用来计算算术表达式的值,例如 ( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) ) 如果将4乘以5,把3 ...
- 算法手记(2)Dijkstra双栈算术表达式求值算法
这两天看到的内容是关于栈和队列,在栈的模块发现了Dijkstra双栈算术表达式求值算法,可以用来实现计算器类型的app. 编程语言系统一般都内置了对算术表达式的处理,但是他们是如何在内部实现的呢?为了 ...
- 利用栈实现算术表达式求值(Java语言描述)
利用栈实现算术表达式求值(Java语言描述) 算术表达式求值是栈的典型应用,自己写栈,实现Java栈算术表达式求值,涉及栈,编译原理方面的知识.声明:部分代码参考自茫茫大海的专栏. 链栈的实现: pa ...
- OpenJudge计算概论-简单算术表达式求值
/*===================================== 简单算术表达式求值 总时间限制: 1000ms 内存限制: 65536kB 描述 2位正整数的简单算术运算(只考虑整数运 ...
- page80-栈用例-算术表达式求值
表达式由括号, 运算符和操作数(数字)组成.我们根据以下4中情况从左到右逐个将这些实体送入栈处理. (1)将操作数压入操作数栈: (2)将运算符压入运算符栈: (3)忽略左括号: (4)在遇到右括号时 ...
- Dijkstra的双栈算术表达式求值算法
这次来复习一下Dijkstra的双栈算术表达式求值算法,其实这就是一个计算器的实现,但是这里用到了不一样的算法,同时复习了栈. 主体思想就是将每次输入的字符和数字分别存储在两个栈中.每遇到一个单次结束 ...
- [Java]算术表达式求值之三(中序表达式转二叉树方案 支持小数)
Entry类 这个类对表达式的合法性进行了粗筛: package com.hy; import java.io.BufferedReader; import java.io.IOException; ...
- java实现算术表达式求值
需要根据配置的表达式(例如:5+12*(3+5)/7.0)计算出相应的结果,因此使用java中的栈利用后缀表达式的方式实现该工具类. 后缀表达式就是将操作符放在操作数的后面展示的方式,例如:3+2 后 ...
- [Java]算术表达式求值之二(中序表达式转后序表达式方案,支持小数)
Inlet类,入口类,这个类的主要用途是验证用户输入的算术表达式: package com.hy; import java.io.BufferedReader; import java.io.IOEx ...
随机推荐
- linux用户管理:创建用户,删除用户,管理用户,用户配置
目录 一.关于用户 二.用户的三种类型 三.与用户有关的配置文件详解 四.创建用户 五.设置用户密码 六.删除用户 七.用户密码时效管理 八.查看用户相关信息的命令 九.修改用户基本信息 十.管理用户 ...
- 前端如何对cookie加密
在前端对 Cookie 进行加密时,你可以使用加密算法对 Cookie 的值进行加密,然后再将加密后的值存储到 Cookie 中.常用的加密算法包括对称加密算法(如 AES)和非对称加密算法(如 RS ...
- js 中你不知道的各种循环测速
在前端 js 中,有着多种数组循环的方式: for 循环: while 和 do-while 循环: forEach.map.reduce.filter 循环: for-of 循环: for-in 循 ...
- zabbix笔记_004 监控Windows主机
zabbix监控Windows主机 zabbix for windows 安装包下载地址: https://www.zabbix.com/cn/download_agents#tab:34 下载zab ...
- rhce练习题容易错的地方
rhce练习题里容易错的地方 使用导航器的时候,ssh连接 因为导航器是一个工具,生成一个容器,在容器里面运行playbook 安装软件包的时候,多个软件包使用循环loop loop的格式 - hos ...
- LeetCode 221. Maximal Square 最大正方形(C++/Java)
题目: Given a 2D binary matrix filled with 0's and 1's, find the largest square containing only 1's an ...
- linux系统下,jdk的安装和配置教程,以jdk-8u311为例
1.官方下载 下载地址:https://www.oracle.com/ 本文以jdk8为例, 1)下载地址:https://www.oracle.com/java/technologies/downl ...
- fontawesome-webfont.woff:1 Failed to load resource: the server responded with a status of 404 ()
fontawesome-webfont.woff2:1 Failed to load resource: the server responded with a status of 404 ()fon ...
- XML文档定义的几种形式和本质区别
XML文档定义的形式 两种定义形式:DTD.Schema DTD:数据类型定义(Data Type Definition),用以描述XML文档的文档结构,是早期的XML文档定义形式. Schema:其 ...
- jsp和servlet的区别、共同点、各自应用的范围?
JSP是Servlet技术的扩展,本质上就是Servlet的简易方式.JSP编译后是"类servlet".Servlet和JSP最主要的不同点在于: Servlet的应用逻辑是在J ...