一、种类介绍

算术表达式有三种:前缀表达式、中缀表达式和后缀表达式。一般用的是中缀,比如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、编码分析

编码工作主要分为以下几块:

  1. 含有函数的中缀表达式转后缀表达式

    • 区分数字、函数名、运算符;
    • 将字符串拆分为数组形式,方便入栈;
    • 判断运算符、函数的优先级,正确排列顺序;
  2. 针对后缀表达式进行计算
    • 区分数字、函数名、运算符的不同操作;
    • 根据运算规则不同进行入栈、出栈和计算;

4、转换流程

  1. 简单算术表达式的中缀转后缀

  1. 带有函数名的表达式,中缀转后缀

5、计算流程

  1. 简单算术后缀表达式计算

  1. 带有函数名的后缀表达式计算

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# 算术表达式求值(后缀法),看这一篇就够了的更多相关文章

  1. 栈的一个实例——Dijkstra的双栈算术表达式求值法

    Dijkstra的双栈算术表达式求值法,即是计算算术表达式的值,如表达式(1 + ( (2+3) * (4*5) ) ). 该方法是 使用两个栈分别存储算术表达式的运算符与操作数 忽略左括号 遇到右括 ...

  2. 【算法】E.W.Dijkstra算术表达式求值

    算术表达式求值 我们要学习的一个栈的用例同时也是展示泛型的应用的一个经典例子,就是用来计算算术表达式的值,例如 ( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) ) 如果将4乘以5,把3 ...

  3. 算法手记(2)Dijkstra双栈算术表达式求值算法

    这两天看到的内容是关于栈和队列,在栈的模块发现了Dijkstra双栈算术表达式求值算法,可以用来实现计算器类型的app. 编程语言系统一般都内置了对算术表达式的处理,但是他们是如何在内部实现的呢?为了 ...

  4. 利用栈实现算术表达式求值(Java语言描述)

    利用栈实现算术表达式求值(Java语言描述) 算术表达式求值是栈的典型应用,自己写栈,实现Java栈算术表达式求值,涉及栈,编译原理方面的知识.声明:部分代码参考自茫茫大海的专栏. 链栈的实现: pa ...

  5. OpenJudge计算概论-简单算术表达式求值

    /*===================================== 简单算术表达式求值 总时间限制: 1000ms 内存限制: 65536kB 描述 2位正整数的简单算术运算(只考虑整数运 ...

  6. page80-栈用例-算术表达式求值

    表达式由括号, 运算符和操作数(数字)组成.我们根据以下4中情况从左到右逐个将这些实体送入栈处理. (1)将操作数压入操作数栈: (2)将运算符压入运算符栈: (3)忽略左括号: (4)在遇到右括号时 ...

  7. Dijkstra的双栈算术表达式求值算法

    这次来复习一下Dijkstra的双栈算术表达式求值算法,其实这就是一个计算器的实现,但是这里用到了不一样的算法,同时复习了栈. 主体思想就是将每次输入的字符和数字分别存储在两个栈中.每遇到一个单次结束 ...

  8. [Java]算术表达式求值之三(中序表达式转二叉树方案 支持小数)

    Entry类 这个类对表达式的合法性进行了粗筛: package com.hy; import java.io.BufferedReader; import java.io.IOException; ...

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

    需要根据配置的表达式(例如:5+12*(3+5)/7.0)计算出相应的结果,因此使用java中的栈利用后缀表达式的方式实现该工具类. 后缀表达式就是将操作符放在操作数的后面展示的方式,例如:3+2 后 ...

  10. [Java]算术表达式求值之二(中序表达式转后序表达式方案,支持小数)

    Inlet类,入口类,这个类的主要用途是验证用户输入的算术表达式: package com.hy; import java.io.BufferedReader; import java.io.IOEx ...

随机推荐

  1. Vue cli之路由router

    一.安装路由 Vue-router用于提供给vue项目在开发中用于绑定url和组件页面的关系的核心插件. 默认情况下,vue没有提供路由的功能,所以我们使用vue-router,并需要在项目根目录. ...

  2. golang 切片原理面试题

    package main import "fmt" func main() { var s = make([]int, 0, 10) _ = append(s, 1,2,3) fm ...

  3. go语言开发的内网穿透工具,frp.

    转载自:https://www.appinn.com/frp/ 什么是 Frp? 内网穿透工具有很多,其中 Frp (Fast Reverse Proxy) 是比较流行的一款.FRP 是一个免费开源的 ...

  4. zabbix笔记_006 zabbix web监控

    web监控 web监控是对http网站服务进行监控,模拟用户访问网站,对特定的结果进行告警,通知管理员网站状态. web监控是运维必备知识点,通过实验能够熟悉配置和了解zabbix是如何监控web站点 ...

  5. MySQL配置主从同步过程记录

    今天由于工作需要,配置了一下主从同步,这里记录一下配置过程,以备查阅. 事先度娘了一番,主从同步需要保证主从服务器MySQL版本一致(我的略有差别,主服务器版本5.5.31,从服务器版本5.5.19) ...

  6. FreeRTOS例程开发

    环境配置 下载官方源码 https://www.freertos.org/ 找到这个,他就是visual studio示例demo,我们主要在这个的基础上修改 下载visio studio https ...

  7. 时间格式化转换及时间比较compareTo,Controller层接收参数格式化,从数据源头解决时间格式错误数据对接口的影响

    时间格式化转换及时间比较compareTo,Controller层接收参数格式化,从数据源头解决时间格式错误数据对接口的影响 /** * 时间格式的转换:在具体报错的地方做转换,可能不能从根本上面解决 ...

  8. python UI自动化,怎么在控制台调试代码?

    Chrom 控制台调试脚本 http://testingpai.com/article/1606720137383 可以在任意网页按 F12 进入开发者工具,选择 console 输入 JS 代码: ...

  9. MAC下Cowardly refusing to 'sudo brew install解决方案

    副标题:<论学习英语的重要性> 在执行'sudo brew install cmake'的时候报错,错误信息如下. (一脸懵逼) 解决方案: 其实报错信息都说好了,大概意思是不能用管理员权 ...

  10. python重拾基础第二天

    本节内容 列表.元祖操作 字符串操作 字典操作 集合操作 文件操作 字符编码与转码 三元运算&生成式&成员运算&解压法&队列堆栈&数据类型转换 1. 列表操作 ...