算符优先文法,中缀式求值,栈的典型应用
栈,是比较基础,应用比较广的一种数据结构,栈和队列都可以看成是比较特殊的一些链表,其最突出的特性就是先进后出。虾米阿尼是一个比较常见的中缀表达式求值的应用,当然中缀式到后缀式的转化也是可以实现的。
中缀式,这个我们不陌生平时大家写的式子就是中缀式的,比如2*(3-1)+(5+6)/9
后缀式,主要考虑的是操作符的位置,操作符在操作数之后的,比如上面的中缀式可以转化为这样的后缀式:2 3 1 - * 5 6 + 9 / +,转化的规则其实也很简单,opnd1 optr opnd2 ==>opnd1 opnd2 optr,大白话来说就是直接把操作符挪到两个操作数的后面,当然了,这里的opnd不单单就是一个数有时可能是中缀式中括号里面的一坨:-)。
表达式求值这一块的内容,本人参看的是以前读数据结构时的经典教材,严蔚敏老师的。
表达式求值的基本原理是一种常见的简明算法,“算符优先文法”,依稀记得这也是编译原理中曾经介绍过的,那么究竟什么是算符优先文法,通俗而言就是我们小学计算四则混合运算的规则:
1)先乘除,后加减
2)同级运算,从左到右依次计算
3)有括号的,先算括号里面的
具体到我们要说的,算符优先文法来看,考察一个式子,比如上面的2*(3-1)+(5+6)/9,这个式子中,我们规定“()”也是一种运算而且这种运算的优先级还比较的高。于是集合上面三条运算的规则,我们可以指定出来一个算符优先级表,计算机扫面表达式,通过查表,获得当前计算的动作。
首先我们先定义我们的运算符表 OP = {+-*/()#},并且我们结合上面的三条运算规则,定义出下面的运算符优先级表:
+ | - | * | / | ( | ) | # | |
+ | > | > | < | < | < | > | > |
- | > | > | < | < | < | > | > |
* | > | > | > | > | < | > | > |
/ | > | > | > | > | < | > | > |
( | < | < | < | < | < | = | > |
) | > | > | > | > | > | ||
# | < | < | < | < | < | = |
观察上面的表格,以第一行为例,当前计算机同时碰到了+和另外一种(也有可能是+)的运算,这是选择运算级别比较高的那种运算,上面 > 说明出在行位置的运算比列位置的运算的优先级高,反则反之,=表明二者的优先级一样,空格表示这两种运算之间没有可比性,说明输入的式子有文法错误。很明显我们要把上面的这个表存储到计算机中,以便计算机在计算的时候方便计算机来查阅,我们一般使用一个二维数组来存储。还有上面的 # 使我们加入的一种运算,我们规定这种运算的优先级最低。
下面就来简述一下,算法核心的流程。
需要两个栈,扫描中缀表达式时,用optr栈存储中缀式里面的运算符,用opnd栈存储中缀式中的运算数。optr栈顶的运算符对应着算符优先级表第一纵裂位置的运算符,每次从中缀式总扫描到的运算符,对应着优先级表第一横行位置的运算符。于是算法的基本思想如下:
(1)首先置操作数栈为空栈,然后置optr运算符栈的栈底为#运算。
(2)依次扫描表达式中的每一个字符ch,若是操作数则压入opnd栈中,若是运算符则和optr栈顶的运算符比较优先级后做相应的操作(若栈顶运算符优先级高则opnd连续弹出两个数完成相应运算再把记过压入opnd中,如果optr栈顶运算符优先级低,则把当前扫描到的运算符压入optr栈中),直至整个表达式扫描完毕,这是opnd中的数值就是运算结果。
具体的描述如下,
/* * 中缀表达式的 求值计算 */ public void inffixCal() { int index = 0; while(inffixExp.charAt(index)!='#' || optr.peekFirst()!='#') { Character ch = inffixExp.charAt(index); if(!inOP(ch)) {// 一般数字 opnd.push(Double.parseDouble(ch+"")); //于是准备取下一个字符了 index ++; } else{// 运算符 switch(Precede(optr.peekFirst(), ch)) { case -1: // < 栈顶 符号 的优先级 低 符号入栈 optr.push(ch); index ++; break; case 1: // > 即栈顶符号的优先级比较高 Character theta = optr.pop(); Number b = (Double) opnd.pop(); Number a = (Double) opnd.pop(); Double c = operate(a, b, theta); opnd.push(c); // 把计算的结果再次压站 break; case 0: // 两种运算符的优先级相等 也就是 () optr.pop(); //脱括号之后 接着往后扫描 index ++; break; default: throw new RuntimeException(new Exception("您的文法有误,请检查")); } } } result = (Double)opnd.pop(); }
算法的实现,定义了一个expressionEvaluate类,具体的实现详见下面的代码,展看查看详情
1 package test; 2 3 import java.util.LinkedList; 4 import java.util.Scanner; 5 6 import javax.naming.InitialContext; 7 8 /* 9 * 基于"算符优先"文法 的表达式求值, 其实这也是数据程序编译器设计中常用的一种方法 10 * 其实可以一次性 扫描中缀表达式的同时,利用算符文法来求出中缀表达式的值,由于中缀式和后缀式的相互转化使用的也是算法文法,这里就把问题分开两部分来做 11 * 提前说一下,个人总是感觉,算法设计类的问题总是比较适合面向过程来解决,这里硬是面向对象感觉有点伪面向对象的感觉 12 * ExpressionEvaluate 算法的主体驱动部分 13 */ 14 15 public class ExpressionEvaluate { 16 17 public static final String OP = "+-*/()#"; //求值系统的所有算附表 18 public static final int[][] opPreTable={ //算符优先级表 19 {1, 1, -1, -1, -1, 1, 1}, 20 {1, 1, -1, -1, -1, 1, 1}, 21 {1, 1, 1, 1, -1, 1, 1}, 22 {1, 1, 1, 1, -1, 1, 1}, 23 {-1, -1, -1, -1, -1, 0, Integer.MAX_VALUE}, 24 {1, 1, 1, 1, Integer.MAX_VALUE, 1, 1}, 25 {-1, -1, -1, -1, -1, -Integer.MAX_VALUE, 0} 26 }; 27 28 // 定义两个工作栈 29 private LinkedList<Character> optr; // 存储操作符 30 private LinkedList<? super Number> opnd; //存储数字 31 private StringBuffer inffixExp; // 存储中缀表达式 32 private StringBuffer suffixExp; //存储后缀表达式 33 private double result; //表达式最终的结果 34 35 {// 构造代码块 优先于 构造函数的执行 36 init(); //初始化操作 37 } 38 public ExpressionEvaluate() { 39 40 } 41 public ExpressionEvaluate(String exp) 42 { 43 setInffixExp(exp); 44 } 45 46 public void setInffixExp (String exp) 47 { 48 inffixExp = new StringBuffer(exp); 49 if(inffixExp.charAt(inffixExp.length()-1)!='#') 50 inffixExp.append('#'); 51 } 52 53 private void init() 54 { 55 inffixExp = new StringBuffer(); 56 suffixExp = new StringBuffer(); 57 optr = new LinkedList<Character>(); 58 opnd = new LinkedList<Number>(); 59 optr.push('#'); 60 result = 0; 61 } 62 63 public String getInffix() 64 { 65 return new String(inffixExp.substring(0, inffixExp.length()-1)); 66 } 67 public String getSuffix() 68 { 69 return new String(suffixExp); 70 } 71 public Double getResult() 72 { 73 return result; 74 } 75 76 /* 77 * 中缀表达式的 求值计算 78 */ 79 public void inffixCal() 80 { 81 int index = 0; 82 83 while(inffixExp.charAt(index)!='#' || optr.peekFirst()!='#') 84 { 85 Character ch = inffixExp.charAt(index); 86 if(!inOP(ch)) 87 {// 一般数字 88 opnd.push(Double.parseDouble(ch+"")); //于是准备取下一个字符了 89 index ++; 90 } 91 else{// 运算符 92 switch(Precede(optr.peekFirst(), ch)) 93 { 94 case -1: // < 栈顶 符号 的优先级 低 符号入栈 95 optr.push(ch); 96 index ++; 97 break; 98 99 case 1: // > 即栈顶符号的优先级比较高 100 Character theta = optr.pop(); 101 Number b = (Double) opnd.pop(); 102 Number a = (Double) opnd.pop(); 103 Double c = operate(a, b, theta); 104 opnd.push(c); // 把计算的结果再次压站 105 break; 106 107 case 0: // 两种运算符的优先级相等 也就是 () 108 optr.pop(); //脱括号之后 接着往后扫描 109 index ++; 110 break; 111 default: 112 throw new RuntimeException(new Exception("您的文法有误,请检查")); 113 } 114 } 115 } 116 result = (Double)opnd.pop(); 117 } 118 119 public double operate(Number a, Number b, Character theta) { 120 double c = 0; 121 switch(theta) 122 { 123 case '+': 124 c = (Double)a + (Double)b; 125 break; 126 case '-': 127 c = (Double)a - (Double)b; 128 break; 129 case '*': 130 c = (Double)a * (Double)b; 131 break; 132 case '/': 133 if((Double)b == 0) 134 throw new RuntimeException(new Exception("除数不能为0")); 135 c = (Double)a / (Double)b; 136 break; 137 default: 138 throw new RuntimeException(new Exception("尚不支持这种运算")); 139 } 140 return c; 141 } 142 /* 143 * 比较栈optr栈顶符号 和 当前符号之间的优先级 144 */ 145 public int Precede(Character peekFirst, Character ch) { 146 return opPreTable[OP.indexOf(peekFirst)][OP.indexOf(ch)]; 147 } 148 // 判断某个字符是不是在 运算符表中 149 public boolean inOP(Character ch) 150 { 151 return OP.contains(new String (ch+"")); 152 } 153 154 /* 155 * 中缀表达式到后缀表达式 156 * 和 中缀式 求值 非常相似 157 */ 158 public void inffix2suffix() 159 { 160 suffixExp.setLength(0); 161 optr.clear(); 162 opnd.clear(); 163 int index = 0; 164 165 } 166 167 public static void main(String[] args) { 168 /* String exp; 169 Scanner scanner = new Scanner(System.in); // 2*(5-1)/2+3-1 170 171 System.out.println("输入一个以#结尾的表达式"); 172 exp = scanner.next();*/ 173 174 ExpressionEvaluate ee = new ExpressionEvaluate(); 175 ee.setInffixExp("2*3*4-1#"); 176 177 System.out.println(ee.getInffix()); 178 179 ee.inffixCal(); 180 181 System.out.println(ee.getResult()); 182 183 184 } 185 186 }
View Code
上面就只实现了中缀表达式的求值,可以进一步完善,inffix2suffix中缀式到后缀式的转化,这里没有实现了,如果实现之后,具体的表达式的计算可以先把中缀式转化为后缀式,计算结果时简单的扫描后缀式就可以计算了,因为后缀式是一种无需担心运算符优先级的算式表达形式。
下面是inffix---》suffix转化的算法描述
(1)标识符号#压入optr栈中,开始扫描终追表达式
(2)当ch!='#'时
if ch>=0 && ch <=9 opnd.push(ch)
if ch is a operator
if ch > optr.top() optr.push(ch)
if ch < optr.top() opnd连续出两个操作数,并和当前的栈顶运算符附加到suffix中
if ch==optr.top() 查看上面的运算符优先级表可以知道,(==)这时候直接把optr的(弹出即可,然后接着向后扫描表达式
转载于:https://www.cnblogs.com/OliverZhang/p/6613560.html
算符优先文法,中缀式求值,栈的典型应用相关推荐
- leetcode 227. Basic Calculator II | 227. 基本计算器 II(中缀表达式求值)
题目 https://leetcode.com/problems/basic-calculator-ii/ 题解 这道题是 中缀表达式求值 的简化版(因为没有左右括号运算),不过输入的形式有两个处理起 ...
- 数据结构实验之栈三:后缀式求值
题目描述 对于一个基于二元运算符的后缀表示式(基本操作数都是一位正整数),求其代表的算术表达式的值. 输入 输入一个算术表达式的后缀式字符串,以'#'作为结束标志. 输出 求该后缀式所对应的算术表达式 ...
- 栈应用:中缀表达式求值
后缀表达式求值比较简单,基本过程为:遇到数字则进栈,遇到运算符则出栈俩数字然后计算结果,再把结果入栈,过程比较简单,不再复习了,下面着重记录中缀表达式求值 中缀表达式求值可以先将中缀转后缀,再用后缀计 ...
- SDUT 2133 数据结构实验之栈三:后缀式求值
后缀式求值的方法参见我的另一篇文章 把运算符变成表达式 1 #include<stdio.h> 2 #include<string.h> 3 int main() 4 { 5 ...
- 编译原理实验(算符优先文法)
work.h 1 #include<iostream> 2 #include<stdlib.h> 3 #include<stdio.h> 4 #includ ...
- java 课程设计表达式求值_NYOJ-35-表达式求值
NYOJ-35-表达式求值 很好的一题,利用栈来计算表达式的值 四则运算的规则:1.先乘除,后加减:2.从左算到右:3.先括号内,后括号外 注意把字符串转换成浮点数可以使用atof函数 #includ ...
- 编译原理算符分析器实现 -* c语言代码,C语言实现算符优先文法源代码(编译原理)...
下面教大家用C语言实现编译原理中的算符优先文法.求出FIRSTVT集和LASTVT集,并判断该文法是否是算符优先文法. 一.知识解析 1.定义 算符优先文法是一种自下而上的分析方法,其文法的特点是文法 ...
- 算符优先文法编写java语法分析器,编译原理课程设计实验报告——基于算符优先分析方法的表达式语法分析器...
内容简介: 一.设计目的 了解用算符优先法对表达进行语法分析的方法,掌握自顶向下的预测语法分析程序的手工构造方法. 二.设计内容 对简单表达式文法构造算符优先分析器. 三.设计要求 1.对下列简单表达 ...
- 编译原理基本定义(文法、算符文法、算符优先文法、算符优先关系表、算符优先分析过程)
文法 文法和语言分为4类. 0型文法:最大类,包含1.2.3型文法. 1型文法:对0型文法来说,所有的产生式的右边的字符长度都要大于左边的字符长度. 2型文法:所有的产生式左边都只有一个字符. 3型文 ...
最新文章
- linux释放内存命令
- [RK3288][Android6.0] 调试笔记 --- 软硬键盘同时使用【转】
- verilog中有符号数运算
- c++调用python3
- 玩转Eclipse — 自动代码规范检查工具Checkstyle
- WebRTC:并非解决音视频应用所有问题的银弹
- Springboot 项目启动后执行某些自定义代码
- java社区活跃度_Java并发编程-活跃度问题
- java实现rtsp转rtmp
- 智能推荐系统之数据预处理
- 蓝牙学习笔记(五)——AC692x_BLE工具make_gatt_services
- jupyter快捷键、markdown语法及markdown的算式语法
- 基于slate构建文档编辑器
- 很少人用的下载者方法
- 前端开发hr面试试题
- 清理数据 python_使用python的数据清理技术
- 无法修改linux密码Authentication token manipulation error 问题解决
- CSS3炫酷发光文字 ,自定义色彩
- 14福师计算机导论在线作业一,(随机)福师18秋《计算机导论》在线作业一资料...
- python 学习分享之简单的播放音乐1(playsound)
热门文章
- (tomcat访问不了的两种解决方法)Bad Request(Invalid Hostname)
- 【网摘】GTK中文显示解决方案
- CCNA11月14日战报
- FoundationDB Record Layer 宣布开源,提供关系数据库功能
- POJ 3461 还是两种方法
- [洛谷P4721]【模板】分治 FFT
- Linux下SVN服务器同时支持Apache的http和https及svnserve独立服务器三种模式且使用相同的访问权限账号...
- 从本机发送信息到另一台服务器上时中文乱码
- linux中文乱码问题及locale详解
- 【pyQuery】抓取startup news首页