什么是前缀表达式、中缀表达式、后缀表达式

前缀表达式、中缀表达式、后缀表达式,是通过树来存储和计算表达式的三种不同方式

以如下公式为例

(a+(b−c))∗d( a+(b-c) )*d(a+(b−c))∗d

通过树来存储该公式,可以表示为


那么问题就来了,树只是一种抽象的数据结构,它必须要通过某个形式的文本来才能存储和输入

此时,就有了三种表示方法:前缀表达式、中缀表达式、后缀表达式

它们分别相当于树的前序遍历、中序遍历、后序遍历,前中后指的是遍历时符号的遍历顺序

前序遍历:符号 - 左操作数 - 右操作数

中序遍历:左操作数 - 符号 - 右操作数

后序遍历:左操作数 - 右操作数 - 符号

中缀表达式

上面的公式,中序遍历的结果为

a+b−c∗da+b-c*da+b−c∗d

显然,这种表达方式是有歧义的,比如ab是一颗子树,cd是一颗子树,最后相减,遍历结果和上面是一样的

所以中缀表达式必须借助括号,才能正确地表达出想要的结果

中缀表达式的表示结果为

(a+(b−c))∗d(a+(b-c))*d(a+(b−c))∗d

这种表达方式,符合人类的阅读习惯

前缀表达式

上面的公式,先序遍历的结果为

∗+a−bcd*+a-bcd∗+a−bcd

这种表达方式是没有歧义的,可以直接作为前缀表达式的结果

这种表达方式,符合计算机的处理习惯,程序可以很容易地解析这种表达式

具体如何解析,下面会给出代码

后缀表达式

上面的公式,后序遍历的结果为

abc−+d∗abc-+d*abc−+d∗

这种表达方式,也符合计算机的处理习惯,解析也很简单

相对于前缀表达式来说,后缀表达式的符号读取顺序,和人类阅读习惯是一致的

因此实际计算机程序中,基本都是用后缀表达式来存储公式的,前缀表达式效果次之

对于中缀表达式,我们则可以先将其转为后缀表达式,再进行求值

通过树结构存储和求值表达式

实现思路比较简单,如果节点上存的是参数,那么该参数的值,就是该节点的值

如果节点上存的操作符,拿该节点左子树和右子树做对应运算,得到的结果作为该节点的值

import java.util.LinkedHashMap;import java.util.Map;public class Demo {//( a+(b-c) )*dpublic static TreeNode createTree() {TreeNode a = new TreeNode();a.data = "a";TreeNode b = new TreeNode();b.data = "b";TreeNode c = new TreeNode();c.data = "c";TreeNode d = new TreeNode();d.data = "d";TreeNode e = new TreeNode();e.data = "e";TreeNode f = new TreeNode();f.data = "f";TreeNode g = new TreeNode();g.data = "g";TreeNode op1 = new TreeNode();op1.data = "*";TreeNode op2 = new TreeNode();op2.data = "+";op1.add(op2);op1.add(d);TreeNode op3 = new TreeNode();op3.data = "-";op2.add(a);op2.add(op3);op3.add(b);op3.add(c);return op1;}//( 3+(4-2) )*10public static Map<String, Integer> createParam() {Map<String, Integer> map = new LinkedHashMap();map.put("a", 3);map.put("b", 4);map.put("c", 2);map.put("d", 10);return map;}public static int getNodeValue(TreeNode<String> node, Map<String, Integer> param) {String data = node.data;switch (data) {case "+": {TreeNode<String> leftValue = node.children.get(0);TreeNode<String> rightValue = node.children.get(1);return getNodeValue(leftValue, param) + getNodeValue(rightValue, param);}case "-": {TreeNode<String> leftValue = node.children.get(0);TreeNode<String> rightValue = node.children.get(1);return getNodeValue(leftValue, param) - getNodeValue(rightValue, param);}case "*": {TreeNode<String> leftValue = node.children.get(0);TreeNode<String> rightValue = node.children.get(1);return getNodeValue(leftValue, param) * getNodeValue(rightValue, param);}case "/": {TreeNode<String> leftValue = node.children.get(0);TreeNode<String> rightValue = node.children.get(1);return getNodeValue(leftValue, param) / getNodeValue(rightValue, param);}default:return param.get(data);}}public static int compute(TreeNode<String> expression, Map<String, Integer> param) {return getNodeValue(expression, param);}public static void main(String[] args) {TreeNode tree = createTree();Map<String, Integer> param = createParam();int result = compute(tree, param);System.out.println(result);}}

前缀表达式解析和求值

∗+a−bcd*+a-bcd∗+a−bcd

首先,我们来观察下前缀表达式的规律

可以发现,每当连续出现两个数值时,前面必定会有一个操作符,这是先序遍历的特征决定的

我们将三个元素取出来进行运行,就可以得到一个操作符节点的数值

如此反复递归,最终就能求出表达式的值

import java.util.*;@SuppressWarnings("all")public class Demo {//表达式final List expression = new ArrayList();//变量值final Map<String, Integer> param = new LinkedHashMap();//用于读取表达式的栈final LinkedList stack = new LinkedList();//( a+(b-c) )*d//( 3+(4-2) )*10public void createExpression() {expression.clear();expression.add("*");expression.add("+");expression.add("a");expression.add("-");expression.add("b");expression.add("c");expression.add("d");}//( a+(b-c) )*d//( 3+(4-2) )*10public void createParam() {param.clear();param.put("a", 3);param.put("b", 4);param.put("c", 2);param.put("d", 10);}//计算表达式的值public int compute() {stack.clear();for (Object element : expression)push(element);Object top = stack.pop();return value(top);}//( a+(b-c) )*d//( 3+(4-2) )*10   //元素入栈//有可以求值的表达式,入栈前先运算,转化为常量再入栈public void push(Object element) {//如果栈为空,直接入栈if (stack.isEmpty()) {stack.push(element);return;}//如果是操作符,直接入栈if (isOperator(element)) {stack.push(element);return;}Object left = stack.getFirst();//如果是数值,且栈顶元素也是数值,直接取出进行运算,得到数值后再入栈if (!isOperator(left)) {stack.removeFirst();Object right = element;Object operator = stack.pop();Integer value = operate(left, right, operator);push(value);return;}//如果栈顶不是数值,直接入栈stack.push(element);}//计算最小表达式运算结果public int operate(Object left, Object right, Object operator) {int L = value(left);int R = value(right);switch (operator.toString()) {case "+":return L + R;case "-":return L - R;case "*":return L * R;case "/":return L / R;default:throw new RuntimeException("unknown operator");}}//获取元素值,元素可能是变量,也可能是常量public Integer value(Object element) {if (element instanceof Integer)return (int) element;return param.get(element);}//判断元素是操作符还是变量或常量public boolean isOperator(Object element) {if (element.equals("+"))return true;if (element.equals("-"))return true;if (element.equals("*"))return true;if (element.equals("/"))return true;return false;}public static void main(String[] args) {Demo demo = new Demo();demo.createExpression();demo.createParam();int result = demo.compute();System.out.println(result);}}

后缀表达式解析和求值

abc−+d∗abc-+d*abc−+d∗

先来观察下后缀表达式的特点,可以发现

由于后缀表达式相当于树的后序遍历,先遍历左子树,再遍历右子树,最后遍历运算符

所以只要有运算符出现的地方,前面两个元素一定是操作数,这样就可以求出对应子树的值

实现代码和前缀表达式非常像,稍微简单一点点

import java.util.*;@SuppressWarnings("all")public class Demo {//表达式final List expression = new ArrayList();//变量值final Map<String, Integer> param = new LinkedHashMap();//用于读取表达式的栈final LinkedList stack = new LinkedList();//( a+(b-c) )*dpublic void createExpression() {expression.clear();expression.add("a");expression.add("b");expression.add("c");expression.add("-");expression.add("+");expression.add("d");expression.add("*");}//( 3+(4-2) )*10public void createParam() {param.clear();param.put("a", 3);param.put("b", 4);param.put("c", 2);param.put("d", 10);}//计算表达式的值public int compute() {stack.clear();for (Object element : expression)push(element);Object top = stack.pop();return value(top);}//元素入栈//有可以求值的表达式,入栈前先运算,转化为常量再入栈public void push(Object element) {//如果不是操作符,直接入栈if (!isOperator(element)) {stack.push(element);return;}//如果是操作符,取出前两个数值,计算出结果,将结果入栈Object right = stack.pop();Object left = stack.pop();Integer value = operate(left, right, element);push(value);}//计算最小表达式运算结果public int operate(Object left, Object right, Object operator) {int L = value(left);int R = value(right);switch (operator.toString()) {case "+":return L + R;case "-":return L - R;case "*":return L * R;case "/":return L / R;default:throw new RuntimeException("unknown operator");}}//获取元素值,元素可能是变量,也可能是常量public Integer value(Object element) {if (element instanceof Integer)return (int) element;return param.get(element);}//判断元素是操作符还是变量或常量public boolean isOperator(Object element) {if (element.equals("+"))return true;if (element.equals("-"))return true;if (element.equals("*"))return true;if (element.equals("/"))return true;return false;}public static void main(String[] args) {Demo demo = new Demo();demo.createExpression();demo.createParam();int result = demo.compute();System.out.println(result);}}

中缀表达式转后缀表达式

中缀表达式直接求值比较麻烦,所以我们将其转换为后缀表达式,再求值就方便了

中缀表达式转后缀表达式的难点在于,要考虑括号和运算符优先级,这里直接给出方案

看的过程中,大家可以拿个Excel,按照下面的流程和公式,一步步把操作记录下来,这样就很直观,容易理解

注意,如果想理解透彻,一定要亲自推演一遍

因为这个转换算法不是凭空产生的,而是根据后缀表达式的特点反推出来的

只有在亲自推演的过程中,才能深刻地理解为什么要这么做

回到正题,继续说转换方案

  1. 创建两个栈,S1用来存输出元素,S2用来存运算符。由于表达式中的运算符是有优先级的,所以必须通过栈来暂存起来
  2. 从中缀表达式栈顶开始,向栈尾逐个读取元素
  3. 如果读到操作数,直接加到S1栈尾。因为后缀表达式操作数永远是在运算符前面的
  4. 如果读到左括号,则直接压入S2栈顶。因为左括号要等到右括号时才能处理
  5. 如果读到运算符,且S2栈为空或S2栈顶元素为左括号,则直接压入S2栈顶。因为这种情况不需要比较运算符优先级
  6. 如果读到运算符,且S2栈顶也为运算符,且当前运算符优先级大于栈顶元素,则将当前运算符压入S2栈顶。因为后面读取到的运算符可能比当前运算符优先级更高,因此暂时不能输出当前运算符
  7. 如果读到运算符,且S2栈顶也为运算符,且当前运算符优先级小于等于栈顶元素,则将S2栈顶运算符弹出,加到S1栈尾。因为优先级高的运算符要先参加运算。注意,这是一个递归过程,因为S2中可能已存在多个运算符,它们的优先级可能都大于等于当前运算符,当这些运算符都弹出时,再将当前运算符压入S2栈顶
  8. 如果读到右括号,则将S2内首个左括号以上的运算符,全部加到S1栈尾。因为括号的优先级是最高的,立刻进行运算

实现代码如下

import java.util.*;@SuppressWarnings("all")public class Demo {//中缀表达式final List expression = new ArrayList();//变量值final Map<String, Integer> param = new LinkedHashMap();//用于输出结果的双向链表final LinkedList s1 = new LinkedList();//用于暂存运算符的栈final LinkedList s2 = new LinkedList();// ((a+b*c+d)+e)*fpublic void createExpression() {expression.clear();expression.add("(");expression.add("(");expression.add("a");expression.add("+");expression.add("b");expression.add("*");expression.add("c");expression.add("+");expression.add("d");expression.add(")");expression.add("+");expression.add("e");expression.add(")");expression.add("*");expression.add("f");}//中缀表达式转后缀表达式// ((a+b*c+d)+e)*f// abc*+d+e+f*public void convert() {s1.clear();s2.clear();//将中缀表达式中的元素逐个输出for (Object element : expression)push(element);//将运算符栈中的剩余元素全部输出while (!s2.isEmpty()) {Object top = s2.pop();s1.addLast(top);}//打印后缀表达式while (!s1.isEmpty()) {Object top = s1.pop();System.out.print(top);}}//输出中缀表达式中的元素逐个输出,输出规则为://1. 创建两个栈,S1用来存输出元素,S2用来存运算符//2. 从中缀表达式栈顶开始,向栈尾逐个读取元素//3. 如果读到操作数,直接加到S1栈尾//4. 如果读到左括号,则直接压入S2栈顶//5. 如果读到运算符,且S2栈为空或S2栈顶元素为左括号,则直接压入S2栈顶//6. 如果读到运算符,且S2栈顶也为运算符,且当前运算符优先级大于栈顶元素,则将当前运算符压入S2栈顶//7. 如果读到运算符,且S2栈顶也为运算符,且当前运算符优先级小于等于栈顶元素,则将S2栈顶运算符弹出,压入S1栈顶(递归)//8. 如果读到右括号,则将S2内首个左括号以上的运算符,全部压入S1栈顶public void push(Object element) {//取出S2栈顶元素,如果为空,则其type为-1Object top = s2.peekFirst();//如果读到操作数,直接加到S1栈尾if (type(element) == 1) {s1.addLast(element);return;}//如果读到左括号,则直接压入S2栈顶if (type(element) == 3) {s2.push(element);return;}//如果读到运算符,且S2栈为空或S2栈顶元素为左括号,则直接压入S2栈顶if (type(element) == 2 && (s2.isEmpty() || type(top) == 3)) {s2.push(element);return;}//如果读到运算符,且S2栈顶也为运算符,且当前运算符优先级大于栈顶元素,则将当前运算符压入S2栈顶if (type(element) == 2 && type(top) == 2 && priority(element) > priority(top)) {s2.push(element);return;}//如果读到运算符,且S2栈顶也为运算符,且当前运算符优先级小于等于栈顶元素,则将S2栈顶运算符弹出,加到S1栈尾(递归)if (type(element) == 2 && type(top) == 2 && priority(element) <= priority(top)) {s2.pop();s1.addLast(top);push(element);return;}//如果读到右括号,则将S2内首个左括号以上的运算符,全部加到S1栈尾if (type(element) == 4) {while (true) {top = s2.pop();if (type(top) == 3)break;s1.addLast(top);}return;}}//判断元素类型//1.操作数 2.运算符 3.左括号 4.右括号public int type(Object element) {if (element == null)return -1;if (element.equals("+"))return 2;if (element.equals("-"))return 2;if (element.equals("*"))return 2;if (element.equals("/"))return 2;if (element.equals("("))return 3;if (element.equals(")"))return 4;return 1;}//判断运算符优先级public int priority(Object element) {if (element.equals("+"))return 1;if (element.equals("-"))return 1;if (element.equals("*"))return 2;if (element.equals("/"))return 2;throw new RuntimeException("unknown operator");}public static void main(String[] args) {Demo demo = new Demo();demo.createExpression();demo.convert();}}

【数据结构与算法】【12】前缀表达式、中缀表达式、后缀表达式相关推荐

  1. Java数据结构和算法(六)——前缀、中缀、后缀表达式

    前面我们介绍了三种数据结构,第一种数组主要用作数据存储,但是后面的两种栈和队列我们说主要作为程序功能实现的辅助工具,其中在介绍栈时我们知道栈可以用来做单词逆序,匹配关键字符等等,那它还有别的什么功能吗 ...

  2. 数据结构 - 栈 (逆波兰计算器)(栈的三种表达式)(前缀、中缀和后缀表达式,后缀也叫逆波兰表达式)(中缀表达式转后缀表达式实现步骤及完整代码)

    栈的三种表达式:前缀.中缀和后缀表达式,后缀也叫逆波兰表达式 前缀(波兰表达式) 中缀(对人来讲很好理解,对于计算机来讲就方便了,一般会把中缀表达式转换成后缀表达式) 后缀(逆波兰表达式) 计算过程 ...

  3. 一行文章让你搞懂什么是前缀、中缀、后缀表达式以及它们之间的相互转换

    一.什么前缀.中缀.后缀表达式(使用 8*(5+6)-1的例子) 1.中缀表达式:8*(5+6)-1:(也就是我们平常所见的运算式) 2.后缀表达式:8 5 6 + * 1 - :计算机是怎么运算的呢 ...

  4. 前缀、中缀、后缀表达式详解

    前缀.中缀.后缀表达式详解 博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢! 介绍 前缀.中缀.后缀表达式是对表达式的不同记法,其 ...

  5. 数据结构与算法——前缀、中缀、后缀表达式

    目录 一.前缀表达式(波兰表达式) 二.中缀表达式 三.后缀表达式(逆波兰表达式) 1.逆波兰计算器 四.中缀转后缀 1.具体步骤 2.代码实现 一.前缀表达式(波兰表达式) 前缀表达式的运算符位于操 ...

  6. 天勤数据结构:前缀、中缀、后缀表达式的转换与计算

    第三章 栈和队列 1. 输出队列问题 2. 表达式的转换 - 手工转换 2.1 中缀表达式 转 前缀表达式 2.2 中缀表达式 转 后缀表达式 2.3 后缀表达式 转 中缀表达式 2.4 后缀表达式 ...

  7. 前缀、中缀、后缀表达式(转载)

    关键字:概念, 前缀表达式, 前缀记法, 中缀表达式, 中缀记法, 波兰式, 后缀表达式, 后缀记法, 逆波兰式 它们都是对表达式的记法,因此也被称为前缀记法.中缀记法和后缀记法.它们之间的区别在于运 ...

  8. 前缀、中缀、后缀表达式及其相互转化的Java实现

    一.中缀表达式转换为前缀.后缀表达式 给个中缀表达式:a+b*c-(d+e) 首先根据运算符的优先级给所有运算单位加括号:((a+(b*c))-(d+e)) 将运算符号移动到对应括号的前面然后去掉所有 ...

  9. 前缀、中缀、后缀表达式

    关键字:概念, 前缀表达式, 前缀记法, 中缀表达式, 中缀记法, 波兰式, 后缀表达式, 后缀记法, 逆波兰式 它们都是对表达式的记法,因此也被称为前缀记法.中缀记法和后缀记法.它们之间的区别在于运 ...

  10. 前缀,中缀,后缀表达式

    前缀表达式 前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前. 例如: ( 3 + 4 ) × 5 − 6 (3+4)×5-6 (3+4)×5−6 对应的前缀表达式就是 - × + 3 4 5 ...

最新文章

  1. 一文把Redis主从复制、哨兵、Cluster三种模式摸透
  2. Python SQLAlchemy
  3. optee的异常向量表-(irq,fiq,svc...)
  4. Linux 内核自解压流程分析
  5. mysql 查数据 default无效_导入mysql数据的时候提示Field * doesn't have a default value解决方法...
  6. 老码农的Java干货资源
  7. Hadoop概念学习系列之谈谈RPC(三十三)
  8. linux下无法删除文件夹,linux服务器下完美解决无法删除虚拟主机文件或文件夹...
  9. 实验报告四 201521430002 张实
  10. vue 交互 HTML,Vue 自定义元素交互
  11. 本草纲目pdf彩图版下载_本草纲目彩色图谱下载|本草纲目彩色图集(精编珍藏版) PDF电子版 - 天天游戏吧...
  12. java开发文档怎么写?教你写java技术文档
  13. 预训练模型MT-BERT的探索和应用
  14. 【OpenCV】—图像对比度、亮度值调整
  15. python鸭制作类代码_python之类的多态(鸭子类型 )、封装和内置函数property
  16. QtAndroid详解 6 集成信鸽推送
  17. Pytorch CPU Tensor与GPU Tensor的运算速度对比测试
  18. DSAPI多功能.NET函数库组件
  19. 【Java】23 函数式编程
  20. 公司生产管理系统如何精细管控生产全过程

热门文章

  1. 2022跨年-跨年倒计时(烟花)
  2. 如何卸载yum安装的软件
  3. DNGuard 2.0 正式版以及DNGuard HVM 预览
  4. 获奖丨泽信荣获“第十届艾景奖”2020年度十佳景观设计奖
  5. 操作系统中并发和并行的区别
  6. matlab优化函数fminunc
  7. 高住房空置率折射出楼市三大问题?
  8. 数学的故事电子书_角度和故事书
  9. MySQL主从复制搭建详解
  10. docker安装redis主从复制模式