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 ...
随机推荐
- 一文看懂Spring事务的七种传播行为
什么叫事务传播行为?听起来挺高端的,其实很简单. 即然是传播,那么至少有两个东西,才可以发生传播.单体不存在传播这个行为. 事务传播行为(propagation behavior)指的就是当一个事务方 ...
- TDSQL数据库考试实操题
第一题: 演练二 物理备份(5分) 答: 第二题:2. 演练一 请根据给定的演练方案,进行相关演练,并按如下要求提交截图 主备切换(5分) 答: 第三题:3. 演练一 请根据给定的演练方案,进行相关演 ...
- HMI-Board上手指南
介绍 HMI-Board为 RT-Thread 联合瑞萨推出的高性价比图形评估套件,取代传统的 HMI+主控板硬件,一套硬件即可实现 HMI+IoT+控制的全套能力.依托于瑞萨高性能芯片 RA6M3 ...
- 使用C代码实现读取文件中的hex字符串,转换为字节数组
举例说明: ptp.txt文件中的内容为: 7a7ac0a8c80100000000003388f70002002c000004000000000000000000000000000000000000 ...
- Java中的变量分类(按照位置分类)
变量按位置分 通过上面类的建立我们又得到了新的概念:成员变量和局部变量 成员变量: 可以使用基本数据类型,也可以使用引用数据类型. java中的变量在使用时必须初始化,成员变量可以不对其初始化,系统会 ...
- ABC340
E 我们可以知道每一个点在每一轮加多少,具体如下: 假如现在操作的点的为 \(k\).那么所有的数都至少会加 \(\dfrac{A_k}{n}\).但是肯定有剩的,剩了 \(A_k \mod n\). ...
- WIN11 删除其它用户/账户
WIN11 删除其它用户/账户 参考:https://support.microsoft.com/zh-cn/windows/%E5%9C%A8%E7%94%B5%E8%84%91%E4%B8%8A% ...
- 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 ...
- 别想宰我,怎么查看云厂商是否超卖?详解 cpu steal time
据说有些云厂商会超卖,宿主有 96 个核心,结果卖出去 100 多个 vCPU,如果这些虚机负载都不高,大家相安无事,如果这些虚机同时运行一些高负载的任务,相互之间就会抢占 CPU,对应用程序有较大影 ...
- com.netflix.hystrix.exception.HystrixBadRequestException: null
com.netflix.hystrix.exception.HystrixBadRequestException: null 排查方法:如果有多个feign接口的调用,可以在每个调用的方法加上try- ...