




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、前缀、中缀、后缀表达式对于未经训练的用户来说,计算机科学领域中数学表达式求值的传统方法即不顺手又难以使用;软件工程师 Nikola.Stepan旨在改变这些传统方法。他的 applet W3Eval对表达式求值与您用纸笔计算的一系列步骤完全一致,但更快并且没有错误。请往下读,了解这一挑战 人类易读的数学到 Java 代码的转换。还记得在您的第一台科学计算器上用逆波兰表示法奋斗的经历吗?W3Eval applet 无法让您可信赖的 HP-41 更易用,正如它的名称所暗示 一个只能运行于 Web 的表达式求值程序。但它的确提供了一种方法 人类更易于遵循的对表达式一步一步的求值。W3Eval的方法与
2、传统计算器不同,却和人类的计算方式一致。当您用传统的计算器计算时,每输入一个新数,前一个数就看不到了。如果在输入一个长表达式中出了错,就得全部重来。有了W3Eval,您就能看到参与计算的所有东西,还能轻松的编辑表达式。它独特的能力(一步一步的对表达式求值)非常容易实现,因为用户能看到求值的每一步,包括临时结果。本文将让您从头至尾认识 W3Eval 功能性的要点;您将看到一些用于表达式求值的代码。不过,我们还是先看看表达式求值的经典算法,这样您就会明白 W3Eval 方法的差异究竟有多少。表达式求值的经典算法编写代码对算术表达式求值的经典方法由 Donald Knuth 描述于 1962 年(请
3、参阅参考资料)。Knuth 将此概括为三个步骤:· 对中缀表达式进行语法分析· 中缀表达式到后缀表达式的转换· 对后缀表达式求值注意到我们谈到的这个经典算法有些简化:算术表达式只包含操作数、二元操作符和一种括号。此外,对于每个操作数和操作符,只用单个字符表示,使语法分析直观。表达式表示法算术表达式中最常见的表示法形式有中缀、前缀和后缀表示法。中缀表示法是书写表达式的常见方式,而前缀和后缀表示法主要用于计算机科学领域。中缀表示法中缀表示法是算术表达式的常规表示法。称它为中缀表示法是因为每个操作符都位于其操作数的中间,这种表示法只适用于操作符恰好对应两个操作数的时候(
4、在操作符是二元操作符如加、减、乘、除以及取模的情况下)。对以中缀表示法书写的表达式进行语法分析时,需要用括号和优先规则排除多义性。Syntax: operand1 operator operand2Example: (A+B)*C-D/(E+F)前缀表示法前缀表示法中,操作符写在操作数的前面。这种表示法经常用于计算机科学,特别是编译器设计方面。为纪念其发明家 Jan Lukasiewicz(请参阅参考资料),这种表示法也称波兰表示法。Syntax : operator operand1 operand2Example : -*+ABC/D+EF后缀表示法在后缀表示法中,操作符位于操作数后面。后
5、缀表示法也称逆波兰表示法(reverse Polish notation,RPN),因其使表达式求值变得轻松,所以被普遍使用。Syntax : operand1 operand2 operatorExample : AB+C*DEF+/-前缀和后缀表示法有三项公共特征:· 操作数的顺序与等价的中缀表达式中操作数的顺序一致· 不需要括号· 操作符的优先级不相关中缀表达式到后缀表达式的转换要把表达式从中缀表达式的形式转换成用后缀表示法表示的等价表达式,必须了解操作符的优先级和结合性。优先级或者说操作符的强度决定求值顺序;优先级高的操作符比优先级低的操作符先求值。 如果
6、所有操作符优先级一样,那么求值顺序就取决于它们的结合性。操作符的结合性定义了相同优先级操作符组合的顺序(从右至左或从左至右)。Left associativity : A+B+C = (A+B)+CRight associativity : ABC = A(BC)转换过程包括用下面的算法读入中缀表达式的操作数、操作符和括号:1. 初始化一个空堆栈,将结果字符串变量置空。 2. 从左到右读入中缀表达式,每次一个字符。3. 如果字符是操作数,将它添加到结果字符串。4. 如果字符是个操作符,弹出(pop)操作符,直至遇见开括号(opening parenthesis)、优先级较低的操作符或
7、者同一优先级的右结合符号。把这个操作符压入(push)堆栈。5. 如果字符是个开括号,把它压入堆栈。6. 如果字符是个闭括号(closing parenthesis),在遇见开括号前,弹出所有操作符,然后把它们添加到结果字符串。7. 如果到达输入字符串的末尾,弹出所有操作符并添加到结果字符串。后缀表达式求值对后缀表达式求值比直接对中缀表达式求值简单。在后缀表达式中,不需要括号,而且操作符的优先级也不再起作用了。您可以用如下算法对后缀表达式求值:1. 初始化一个空堆栈2. 从左到右读入后缀表达式3. 如果字符是一个操作数,把它压入堆栈。4. 如果字符是个操作符,弹出两个操作数,执行恰当操作,然后
8、把结果压入堆栈。如果您不能够弹出两个操作数,后缀表达式的语法就不正确。5. 到后缀表达式末尾,从堆栈中弹出结果。若后缀表达式格式正确,那么堆栈应该为空。W3Eval:一种新的方法W3Eval的方法与上面概括的经典算法不同。不是把中缀表达式转换为后缀表示法;恰恰相反,它对中缀表达式直接求值。这种方法比传统方法稍微复杂了些,但它支持一步一步的求值,在执行时您能看到每一步。求值过程类似于手工计算:如果表达式中包含括号,先求嵌套最深的括号对中的子表达式的值。所有括号内的子表达式都求值完毕后,表达式的其它部分再求值。求值过程分为三个步骤:1. 表达式语法分析2. 表达式检查3. 一步一步的求值表达式语法
9、分析W3Eval 的数学表达式由数字、变量、操作符、函数和括号组成。除了缺省的十进制计数制外 W3Eval 还支持二进制、八进制和十六进制。这些以其它计数制计数的数必须以 # 开头,并紧跟 b、o 或者 h 来分别表示二进制、八进制或十六进制。W3Eval 的变量是不限长度的大写字母和数字序列,其首字符必须是字母。W3Eval 有一些预定义的变量,不过它也支持用户定义的变量。W3Eval 支持带有固定或不定数量自变量的函数。 函数可分为以下几组:· 三角函数(sin、cos、tan、cot、sec、csc)· 反三
10、角函数(asin、acos、atan、atan2、acot、asec、acsc)· 双曲线函数(sinh、cosh、tanh、coth、sech、csch)· 反双曲线函数(asinh、acosh、atanh、acoth、asech、acsch)· 指数函数(log、log2、log10、exp、exp2、exp10、sqrt、cur)· 组合学函数(Combinatoric)(comb、combr、perm、permr、var、varr)· 统计函数(sum、avg、min、max、stddev、count)· 其它(abs、cei
11、l、fact、floor、pow、random、rint、round、sign、frac、hypot、deg、rad、trunc、int)W3Eval 对表达式进行语法分析,也就是指它识别出表达式的算术成分,并将它们转化成语言符号(token),然后把它们放入向量。表达式一旦处于这种状态,就为下面两步做好了准备:表达式检查和求值。W3Eval 的符号(token)是算术表达式的组成部分;记号(mark) 是独立的字符, 由 applet 使用,作为识别各种符号的内部标志。每种符号有唯一的 mark 与之对应。W3Eval 的表达式由表 1 所示的符号组成。表 1. W3Eval 的符
12、号TokenMark类十进制数Double二进制数String十六进制数String八进制数String变量Variable函数Function操作符Operator开括号String闭括号String逗号String 用以表示函数、操作符和变量类的定义如清单 1 所示:清单 1. Function、Operator 和 Variable 类的定义public class Function public String function; public int number_of_arguments; public Function( String function, int numb
13、er_of_arguments ) this.function=function; this.number_of_arguments=number_of_arguments; public String toString() return function; public class Operator public String operator; public byte priority; public Operator( String operator, byte priority ) this.operator=operator; this.priority=priority; publ
14、ic String toString() return operator; public class Variable public String variable; public double value; public Variable( String variable, double value ) this.variable=variable; this.value=value; public String toString() return variable; Token 类如清单 2 所示。清单 2. Token 类public class Token public Ob
15、ject token; public char mark; public int position; public int length; public Token ( Object token, char mark, int position, int length ) this.token=token; this.mark=mark; this.position=position; this.length=length; public String toString() return token.toString()+" ; "+mark+" ; "
16、+position+" ; "+length+"" 表达式检查检查正规表达式正确性的所有代码都在一个独立的类中。详细的表达式检查能够确定错误确切的类型和位置。 错误检查有七类:括号检查。W3Eval 的表达式可以包含三种括号:标准圆括号、方括号和花括号。如果表达式包含相同数量的开括号和闭括号,并且每个开括号与一个相应的同种闭括号相匹配,则表达式的括号语法正确。三种括号在语义上等价,如下面的代码段所示。清单 3. 三种括号import java.util.Stack;public class Parentheses_check public static
17、boolean is_open_parenthesis( char c ) if ( c=( &line;&line; c= &line;&line; c= ) return true; else return false; public static boolean is_closed_parenthesis( char c ) if ( c=) &line;&line; c= &line;&line; c= ) return true; else return false; private static boolean par
18、entheses_match( char open, char closed ) if ( open=( && closed=) ) return true; else if ( open= && closed= ) return true; else if ( open= && closed= ) return true; else return false; public static boolean parentheses_valid( String exp ) Stack s = new Stack(); int i; char curr
19、ent_char; Character c; char c1; boolean ret=true; for ( i=0; i current_char=exp.charAt( i ); if ( is_open_parenthesis( current_char ) ) c=new Character( current_char ); s.push( c ); else if ( is_closed_parenthesis( current_char ) ) if ( s.isEmpty() ) ret=false; break; else c=(Character)s.pop(); c1=c
20、.charValue(); if ( !parentheses_match( c1, current_char ) ) ret=false; break; if ( !s.isEmpty() ) ret=false; return ret; token 检查。检查表达式语法。确保表达式所有部分都被认为是合法的。表达式开头的检查(请参阅清单 4)。确保表达式从合法的符号开始。不可以用操作符、逗号或闭括号作为表达式的开始符。清单 4. 正确的表达式开头的检查private static boolean begin_check( Vector tokens, Range r, StringBuffe
21、r err ) char mark; Token t; t=(Token)tokens.elementAt( 0 ); mark=t.mark; if ( mark=P ) err.append( Messages.begin_operator ); else if ( mark=) ) err.append( Messages.begin_parenthesis ); else if ( mark=Z ) err.append ( Messages.begin_comma ); else return true; r.start=0; r.end=t.length; return false
22、; 表达式末尾的检查。确保表达式以合法符号结束。不可以用操作符、函数、逗号或开括号作为表达式结束符。符号序列的检查。检查表达式中的符号序列。在下面的表格中,若 X 轴上的符号和 Y 轴上的符号对应的交界处用 X 作了记号,则相应 X 轴上的符号可以接在 Y 轴上符号的后面。表 2. 合法的符号序列_DBHOVFP()ZD_犠_犠犠B_犠_犠犠H_犠_犠犠O_犠_犠犠V_犠_犠犠F_犠_P犠犠犠犠犠犠_犠_(犠犠犠犠犠犠_犠_)_犠_犠犠Z犠犠犠犠犠犠_犠_函数检查。确保表达式中所有函数的自变量数量正确。逗号检查。逗号只能用于分隔函数的自变量。若用于表达式其它地方,就不合法。一步一步
23、的求值只有能顺利通过以上概括的所有检查的表达式,W3Eval 才求值。从而确保内建于 W3Eval 中的前提条件不会出现问题。后面的算法用于单步执行表达式求值:1. 找出嵌入最深的那对括号。2. 在这对括号中,找出优先级最高的操作符。3. 若这对括号中没有操作符:o 如果表达式再不包含任何其它的括号,求值(过程)完成。o 如果表达式包含括号,但不包含操作符,则存在一个函数。对函数求值,然后转到步骤 5。4. 获取操作数并执行运算。5. 从向量中除去用过的符号并在同一位置放入结果。6. 除去冗余括号。7. 将向量中剩余的符号结合到字符串并在屏幕上显示结果。现在,我们将更为详细的查看算法的每一步,
24、同时查看大部分有意思的代码片段。步骤 1:为避免括号的处理,W3Eval 确定哪个子表达式处于嵌套最深的那对括号中。这项任务需要两步。第一步,W3Eval 必须找出第一个闭括号:清单 5. 找出第一个闭括号public static int pos_first_closed_parenthesis( Vector tokens ) Token t; for ( int i=0; i t=(Token)tokens.elementAt( i ); if ( t.mark=) ) return i; return 0; 第二步,找出与第一步找到的闭括号相匹配的开括号,如清单 6 所示。清单 6.
25、找出匹配的开括号public static int pos_open_parenthesis( Vector tokens, int closed_parenthesis ) int i; Token t; i=closed_parenthesis-2; while ( i>=0 ) t=(Token)tokens.elementAt( i ); if ( t.mark=( ) return i; i-; return 0; 步骤 2:要实现求值的单步执行,W3Eval 在嵌套最深的那对括号中找出优先级最高的操作符。(操作符的优先级已硬编码到 applet 中;请参阅参考资料
26、以获取完整的代码清单。)清单 7. 找出优先级最高的操作符public static int pos_operator( Vector tokens, Range r ) byte max_priority=Byte.MAX_VALUE; int max_pos=0; byte priority; String operator; Token t; for ( int i=r.start+2; i t=(Token)tokens.elementAt( i ); if ( t.mark!=P ) continue; priority=(Operator)t.token).priority; op
27、erator=(Operator)t.token).operator; if ( priority operator.equals("*") ) && priority = max_priority ) max_priority=priority; max_pos=i; return max_pos; 步骤 3:如果表达式中不包含其它括号,求值的过程就完成。如果表达式包含括号,但不包含操作符,则存在需要求值的函数。清单 8. 检查是否还有其它操作符.int poz_max_op=pos_operator( tokens, range );/ if there
28、 are no operatorsif ( poz_max_op=0 ) if ( no_more_parentheses ) return false; else double result; result=function_result( tokens, range.start-1 ); function_tokens_removal( tokens, range.start-1 ); t = new Token ( new Double(result), D, 0, 0 ); tokens.setElementAt( t, range.start-1 ); parentheses_rem
29、oval( tokens, range.start-1 ); return true; .步骤 4:所有的操作符都是二元的,也就是说第一个操作数位于操作符之前,第二个操作符位于操作符之后。清单 9. 获取操作数并执行运算.double operand1, operand2;/ first operand is before.t=(Token)tokens.elementAt( poz_max_op-1 );operand1=operand_value( t );/ .and second operand is after operatort=(Token)tokens.elementAt( p
30、oz_max_op+1 );operand2=operand_value( t );/ operatort=(Token)tokens.elementAt( poz_max_op );String op=(Operator)t.token).operator;double result=operation_result( operand1, operand2, op );tokens.removeElementAt( poz_max_op+1 );tokens.removeElementAt( poz_max_op );t = new Token ( new Double(result), D
31、, 0, 0 );tokens.setElementAt( t, poz_max_op-1 );parentheses_removal( tokens, poz_max_op-1 );.操作数可以是变量,还可以是十进制、十六进制、八进制或二进制数。清单 10. 获取操作数public static double operand_value( Token t ) if ( t.mark=V ) return (Variable)t.token).value; else if ( t.mark=D ) return (Double)t.token).doubleValue(); else if (
32、 t.mark=H ) return base_convert( (String)t.token).substring(2), 16 ); else if ( t.mark=O ) return base_convert( (String)t.token).substring(2), 8 ); else if ( t.mark=B ) return base_convert( (String)t.token).substring(2), 2 ); 接下来的方法将不同计数制的数转化为十进制的形式。清单 11. 将数转化为十进制数public static long base_convert( S
33、tring s, int base ) long r=0; int i, j; for ( i=s.length()-1, j=0; i>=0; i-, j+ ) r=r+digit_weight( s.charAt( i ) )*(long)Math.pow( base, j ); return r; public static int digit_weight( char c ) if ( Character.isDigit( c ) ) return c-48; else if ( A return c-55; else if ( a return c-87; return -1;
34、 一旦确定操作数和操作符后,就可以执行运算了,如清单 12 所示。步骤 5:在这步中,W3Eval 从向量中除去用过的符号并在同一位置放入结果。对于函数求值这类情况,除去的是函数、括号、自变量和逗号;而对于操作符求值这类情况而言,除去的则是操作数和操作符。步骤 6:在求值的这一步,W3Eval 从表达式中除去冗余括号。清单 13. 除去冗余括号private static void parentheses_removal( Vector tokens, int pos ) if ( pos>1 &&&& (Token)tokens
35、.elementAt( poz-2 ).mark!=F &&&& (Token)tokens.elementAt( poz-1 ).mark=( &&&& (Token)tokens.elementAt( poz+1 ).mark=) &line;&line; pos=1 &&&& (Token)tokens.elementAt( 0 ).mark=( &&&& (Token)tokens
36、.elementAt( 2 ).mark=) ) tokens.removeElementAt( poz+1 ); tokens.removeElementAt( poz-1 ); return; 步骤 7:在求值的最后一步,向量中剩余的符号被结合到字符串,并在屏幕上显示。清单 14. 结合符号并显示结果public static String token_join( Vector tokens ) String result=new String(); Token t; for ( int i=0; i t=(Token)tokens.elementAt( i ); if ( t.mark=
37、D ) double n=(Double)t.token).doubleValue(); result=result + formated_number( n ); else result=result + t.token; if ( result.endsWith( ".0" ) ) result=result.substring( 0, result.length()-2 ); result=result + " " return result; 结论本文分析了一个 applet ,它能一步一步的对算术表达式求值。同时还按顺序回顾了最有意思的代码片段,并论述了两种不同的表达式求值方法。
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 新疆维吾尔自治区喀什地区2024-2025学年七年级下学期5月期中数学试卷(含解析)
- 音乐教师心理素质增强计划
- 地下管线及智能监测设施保护加固措施
- 市政工程设备及材料供应方案计划
- 新人教版二年级语文教学创新计划
- 2025年五年级科学课程教学计划
- 招商专员的项目评估职责
- 武汉理工大学自主招生个人陈述表达范文
- 以小见大作文范文心得体会
- 建材供应供货进度计划的保证措施
- GB/T 22562-2008电梯T型导轨
- 中国瓷器介绍英文版教学内容
- 三基院感试题和答案
- 综合英语(3)-国家开放大学电大学习网形考作业题目答案
- 中医内科高级副高级职称考试模拟试题及答案
- 会议签到表及会议记录
- 测量仪器自检记录表(全站仪)
- 甘肃省教育科学规划20XX年度课题申请申报表
- 铁板神数计算取数方法
- 10、连乘、连除和乘除混合运算
- 《平行四边形》PPT课件共(25张PPT)
评论
0/150
提交评论