一、种类介绍

算术表达式有三种:前缀表达式、中缀表达式和后缀表达式。一般用的是中缀,比如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. go强大的垃圾回收机制。

    一.C语言中返回函数中局部变量值和指针(1) 在C语言中,一个函数可以直接返回函数中定义的局部变量,其实在函数返回后,局部变量是被系统自动回收的,因为局部变量是分配在栈空间,那为什么还可以返回局部变量 ...

  2. NumPy 泊松分布模拟与 Seaborn 可视化技巧

    泊松分布 简介 泊松分布是一种离散概率分布,用于描述在给定时间间隔内随机事件发生的次数.它常用于模拟诸如客户到达商店.电话呼叫接入中心等事件. 参数 泊松分布用一个参数来定义: λ:事件发生的平均速率 ...

  3. Codes 重新定义 SaaS 模式的研发项目管理平台开源版 4.5.3 发布

    一:简介 Codes 重新定义 SaaS 模式 = 云端认证 + 程序及数据本地安装 + 不限功能 + 30 人免费  Codes  是一个 高效.简洁.轻量的一站式研发项目管理平台.包含需求管理,任 ...

  4. js 实现仿百度换肤效果

    图片自行换掉即可查看效果,原理就是基于tab切换的效果实现的 效果图 1 <!DOCTYPE html> 2 <html> 3 4 <head> 5 <met ...

  5. ARM汇编基础

    1 GNU语法 1.1 GNU汇编 GNU 汇编语法适用于所有的架构,并不是 ARM 独享的,GNU 汇编由一系列的语句组成,每行一条语句,每条语句有三个可选部分,如下: label: instruc ...

  6. 2024 Web 新特性 - 使用 Popover API 创建弹窗

    Popover API 为开发者提供了一种声明式的方式来创建各种类型的弹窗.目前已在所有三大浏览器引擎中可用,并正式成为 Baseline 2024 的一部分. 一直以来,我们在实现弹出式菜单.提示框 ...

  7. kettle从入门到精通 第六十三课 ETL之kettle kettle调用python脚本的两种方法

    kettle中不能直接调用python脚本,可以通过shell脚本和http进行调用pyton服务. 一.shell脚本调用python脚本 1.下面是一段简单的无参python脚本 import o ...

  8. 网络诊断工具traceroute的使用

    在 Linux 系统中,traceroute 是一个网络诊断工具,用于确定数据包从你的计算机到目标主机(如一个网站或远程服务器)所经过的路由路径. 如果你的系统中没有安装 traceroute,你可以 ...

  9. 搭建springboot redis项目

    1.创建项目 如果出现init失败(需要等待网络可以正常连接) 或者运行主类的时候报错(错误: 找不到或无法加载主类),需要重新导入maven项目再重新编译试试. 2.引入pom jar <?x ...

  10. C++判断字符串是否相等

    话不多说,上代码 bool str_same(char *p1, char *p2) { if (strcmp(p1, p2)) return 0; else for (int i = 0; i &l ...