- 浏览: 5958 次
- 性别:
- 来自: 上海
最新评论
-
spiniper:
smallbug_vip 写道这么好的文章为什么没人呢, 大赞 ...
IT面试杂谈 -
smallbug_vip:
这么好的文章为什么没人呢, 大赞一个
IT面试杂谈
此文章献给那些正在编程道路上艰难学习探索的同行们或者未来的同行们。这是我自己实现的。
此算法被分为三个步骤:
• 对中缀表达式进行语法分析
• 中缀表达式到后缀表达式的转换
• 对后缀表达式求值
中缀表示法为:
(A+B)*C-D/(E+F)
此表示法是为人类所接受的表示法,通俗易懂,结构清晰
后缀表示法为:
AB+C*DEF+/-
此表示法为计算机易识别的表示法,如果不是脑部发达的天才,估计很难看懂。
然后是具体算法实现步骤:
1. 初始化一个空堆栈,将结果字符串变量置空。
2. 从左到右读入中缀表达式,每次一个字符。
3. 如果字符是操作数,将它添加到结果字符串。
4. 如果字符是个操作符,弹出(pop)操作符,直至遇见开括号(opening parenthesis)、优先级较低的操作符或者同一优先级的右结合符号。把这个操作符压入(push)堆栈。
5. 如果字符是个开括号,把它压入堆栈。
6. 如果字符是个闭括号(closing parenthesis),在遇见开括号前,弹出所有操作符,然后把它们添加到结果字符串。
7. 如果到达输入字符串的末尾,弹出所有操作符并添加到结果字符串。
这个许多地方已有相关文章,此处贴出来便于与我代码对照。
下面就是具体的代码实现了:
首先要分析中缀表达式包含哪些元素,如何完成计算过程,怎样方便扩展与抽象,然后怎样方便用户程序调用所实现的功能等......
第一步,确定客户程序如何最方便的调用我们写的工具,调用必须包含哪些元素:
用户程序调用必须有一个函数,函数必须传一个参数,就是表达式字符串,然后这个函数返回一个浮点型的计算结果,即是double result=object.method("xxxx");
然而这里首先要确定此处是个静态方法还是对象方法,我所选择的是对象方法,因为静态方法难以扩展,对象方法可以往对象中放入各种参数然实现不同形式的计算,因此此处使用的是对象方法,对此我建立一个类,不过在类建立之前我们需要建立包,这个包即是整个该功能承载的所有组件的包名,我命名此包为org.spiniper.commons.tool.expression。然后在此包下建立此组件核心类OperatorExpression也就是org.spiniper.commons.tool.expression.OperatorExpression,然后在此核心类下建立一个核心方法,此方法为唯一对外接口,其他客户程序通过此方法完成对所有组件的调用:
public double getDouble(String express)即是org.spiniper.commons.tool.expression.OperatorExpression:getDouble(Ljava.lang.String)D
有了主要对外的接口,接下来就要分析计算过程,算术的计算过程一般都是两个数与一个操作符来得到一个结果的方式,无论表达式多复杂,都可以拆成若干个如此的子运算,而子运算存在不同类型的运算方式(加、减、乘、除),因此运算是一个存在多态类型的抽象,因此定义一个字运算类型的接口org.spiniper.commons.tool.expression.operator.Operator,我命名其为运算符接口,此接口需定义一必须实现的方法,接口代码如下:
接口定义完成以后,我们就可以完成实现此接口对应的一些具体的运算符类,在此就不一一列出,只给一个加法的运算示例
接下来就是算法的实现了,以下是类OperatorExpression的代码,此代码首先确定好构造方法,构造方法先要初始化一些基本的运算符方法,然后也需要能够添加新的运算符的构造方法,同时一些对运算符读写的方法也需要建立。在此实现中,我将中缀转后缀与计算结果的逻辑写在两个不同的方法中,代码如下:
在此其中,我自定义了一些异常,通常自定义一些异常来抛出不正确的输入给用户程序,让错误的程序不会继续执行,并提示客户程序有效的错误信息是很必要的,在此要说明,尽量不要抛出jdk的内置异常,这些异常并没有做什么特殊设计,完全可以自己实现。
这些代码中我初步实现了一些支持函数的代码,但函数并不支持表达式的嵌套运算,因为此示例仅供学习使用,因此也未做任何性能上的处理,只是简单的算法实现。关于函数接口的代码如下,同时提供一个简单的实现:
实现:
此算法被分为三个步骤:
• 对中缀表达式进行语法分析
• 中缀表达式到后缀表达式的转换
• 对后缀表达式求值
中缀表示法为:
(A+B)*C-D/(E+F)
此表示法是为人类所接受的表示法,通俗易懂,结构清晰
后缀表示法为:
AB+C*DEF+/-
此表示法为计算机易识别的表示法,如果不是脑部发达的天才,估计很难看懂。
然后是具体算法实现步骤:
1. 初始化一个空堆栈,将结果字符串变量置空。
2. 从左到右读入中缀表达式,每次一个字符。
3. 如果字符是操作数,将它添加到结果字符串。
4. 如果字符是个操作符,弹出(pop)操作符,直至遇见开括号(opening parenthesis)、优先级较低的操作符或者同一优先级的右结合符号。把这个操作符压入(push)堆栈。
5. 如果字符是个开括号,把它压入堆栈。
6. 如果字符是个闭括号(closing parenthesis),在遇见开括号前,弹出所有操作符,然后把它们添加到结果字符串。
7. 如果到达输入字符串的末尾,弹出所有操作符并添加到结果字符串。
这个许多地方已有相关文章,此处贴出来便于与我代码对照。
下面就是具体的代码实现了:
首先要分析中缀表达式包含哪些元素,如何完成计算过程,怎样方便扩展与抽象,然后怎样方便用户程序调用所实现的功能等......
第一步,确定客户程序如何最方便的调用我们写的工具,调用必须包含哪些元素:
用户程序调用必须有一个函数,函数必须传一个参数,就是表达式字符串,然后这个函数返回一个浮点型的计算结果,即是double result=object.method("xxxx");
然而这里首先要确定此处是个静态方法还是对象方法,我所选择的是对象方法,因为静态方法难以扩展,对象方法可以往对象中放入各种参数然实现不同形式的计算,因此此处使用的是对象方法,对此我建立一个类,不过在类建立之前我们需要建立包,这个包即是整个该功能承载的所有组件的包名,我命名此包为org.spiniper.commons.tool.expression。然后在此包下建立此组件核心类OperatorExpression也就是org.spiniper.commons.tool.expression.OperatorExpression,然后在此核心类下建立一个核心方法,此方法为唯一对外接口,其他客户程序通过此方法完成对所有组件的调用:
public double getDouble(String express)即是org.spiniper.commons.tool.expression.OperatorExpression:getDouble(Ljava.lang.String)D
有了主要对外的接口,接下来就要分析计算过程,算术的计算过程一般都是两个数与一个操作符来得到一个结果的方式,无论表达式多复杂,都可以拆成若干个如此的子运算,而子运算存在不同类型的运算方式(加、减、乘、除),因此运算是一个存在多态类型的抽象,因此定义一个字运算类型的接口org.spiniper.commons.tool.expression.operator.Operator,我命名其为运算符接口,此接口需定义一必须实现的方法,接口代码如下:
package org.spiniper.commons.tool.expression.operator; /** * 此接口表示数学运算符类型。 * @author * */ public interface Operator { /** * 运算符计算方式方法。 * @param x 左操作数。 * @param y 右操作数。 * @return 计算结果。 */ long getLong(long x,long y); /** * 运算符计算方式方法。 * @param x 左操作数。 * @param y 右操作数。 * @return 计算结果。 */ double getDouble(double x,double y); /** * 运算符所对应的算术符号。 * @return 运算符字符。 */ char getFlag(); /** * 运算符所对应的数学名称。 * @return 运算符名称。 */ String getMathName(); /** * 运算符的优先级,数值越高的优先级越高。 * @return 优先级数值。 */ int getPriority(); /** * 是否为左结合操作符,跟运算符的结核性有关。 * @return true为左结合,false为由结合。 */ boolean isLeftLink(); }
接口定义完成以后,我们就可以完成实现此接口对应的一些具体的运算符类,在此就不一一列出,只给一个加法的运算示例
package org.spiniper.commons.tool.expression.operator.commons; import org.spiniper.commons.tool.expression.operator.Operator; public class Addition implements Operator { public double getDouble(double x, double y) { return x+y; } public char getFlag() { return '+'; } public long getLong(long x, long y) { return x+y; } public String getMathName() { return "+"; } public int getPriority() { return 1; } public boolean isLeftLink() { return true; } }
接下来就是算法的实现了,以下是类OperatorExpression的代码,此代码首先确定好构造方法,构造方法先要初始化一些基本的运算符方法,然后也需要能够添加新的运算符的构造方法,同时一些对运算符读写的方法也需要建立。在此实现中,我将中缀转后缀与计算结果的逻辑写在两个不同的方法中,代码如下:
package org.spiniper.commons.tool.expression; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.spiniper.commons.tool.Validator; import org.spiniper.commons.tool.expression.exceptions.NotNumberException; import org.spiniper.commons.tool.expression.exceptions.WrongExpressException; import org.spiniper.commons.tool.expression.exceptions.WrongOperatorFlagException; import org.spiniper.commons.tool.expression.function.Function; import org.spiniper.commons.tool.expression.function.commons.Power; import org.spiniper.commons.tool.expression.function.commons.Sin; import org.spiniper.commons.tool.expression.operator.Operator; import org.spiniper.commons.tool.expression.operator.commons.Addition; import org.spiniper.commons.tool.expression.operator.commons.Division; import org.spiniper.commons.tool.expression.operator.commons.Logarithm; import org.spiniper.commons.tool.expression.operator.commons.Multiplication; import org.spiniper.commons.tool.expression.operator.commons.PowerOf; import org.spiniper.commons.tool.expression.operator.commons.Roots; import org.spiniper.commons.tool.expression.operator.commons.Subtraction; /** * 运算表达式操作对象,将表达式转化为其运算结果。 * @author 。 * */ public class OperatorExpression { public static final Pattern functionregex = Pattern.compile("((\\(|\\+|-|\\*|\\/)|^)(([_a-zA-Z]\\w*)\\((\\d+(.\\d+)?(,\\d+(.\\d+)?)*)\\))((\\)|\\+|-|\\*|\\/)|$)"); public OperatorExpression(){ this.operators=new HashMap<Character, Operator>(); this.functions=new HashMap<String, Function>(); try{ this.putOperator(new Addition()); this.putOperator(new Subtraction()); this.putOperator(new Multiplication()); this.putOperator(new Division()); this.putOperator(new PowerOf()); this.putOperator(new Roots()); this.putOperator(new Logarithm()); this.putFunction(new Power()); this.putFunction(new Sin()); }catch(Exception e){ } } public OperatorExpression(Map<Character,Operator> operators){ this.operators=operators; } private Map<Character, Operator> operators; private Map<String, Function> functions; static{ } /** * 获得表达式运算结果,结果类型为long类型 * @param expression 运算表达式字符串 * @return 运算结果 * @throws NotNumberException 当表达式的首字符不是数字或者括号时,抛出异常。 * @throws WrongExpressException 当运算表达式格式不正确时,抛出异常。 */ public long getLong(String expression) throws NotNumberException,WrongExpressException{ List<Object> list= this.infixToSuffix(expression); return (long)this.operator(list); } /** * 获得表达式运算结果,结果类型为double类型 * @param expression 运算表达式字符串 * @return 运算结果 * @throws NotNumberException 当表达式的首字符不是数字或者括号时,抛出异常。 * @throws WrongExpressException 当运算表达式格式不正确时,抛出异常。 */ public double getDouble(String expression) throws NotNumberException,WrongExpressException{ List<Object> list= this.infixToSuffix(expression); return this.operator(list); } /** * 添加新的运算符号以及运算方式。 * @param operator 运算符对象。 * @throws WrongOperatorFlagException 如果运算符对象的符号错误时抛出异常。 */ public void putOperator(Operator operator) throws WrongOperatorFlagException{ if(Validator.isNumber(String.valueOf(operator.getFlag()))){ throw new WrongOperatorFlagException("不能以数字作为符号。"); } this.operators.put(operator.getFlag(), operator); } public void putFunction(Function function) throws WrongOperatorFlagException{ this.functions.put(function.getName(), function); } /** * 判断某字符为合法运算符号。 * @param flag 运算符字符。 * @return true为合法符号,false为不合法符号。 */ public boolean isFlag(char flag){ Set<Character> set=this.operators.keySet(); for (Character character : set) { if(flag==character.charValue()){ return true; } } return false; } /** * 根据字符获得已经使用的运算符对象。 * @param flag 运算符字符。 * @return 运算符对象。 */ public Operator getOperator(char flag){ return operators.get(flag); } /** * 获得所有支持操作符 * @return 所有支持操作符 */ public Map<Character, Operator> getOperators(){ return (Map<Character, Operator>)((HashMap<Character, Operator>)operators).clone(); } //将中缀表达式转化为后缀表达式。 private List<Object> infixToSuffix(String expression) throws WrongExpressException{ expression=this.parseFunExpress(expression); List<Object> result=new ArrayList<Object>(); Stack<Character> operator=new Stack<Character>(); StringBuffer numbers=new StringBuffer(); char[]chars=expression.toCharArray(); char readchar=0; for (int i = 0; i < chars.length; i++) { if(i==0&&!Validator.isNumber(String.valueOf(chars[i]))){ if(chars[i]!='(') throw new NotNumberException("表达式格式不正确,首字符必须是数值"); } if(Validator.isNumber(String.valueOf(chars[i]))){ if(readchar==')'){ throw new WrongExpressException("表达式格式不正确:第"+(i+1)+"个字符'"+chars[i]+"'出现错误,括号不能与数字连接。"); } numbers.append(chars[i]); }else if(chars[i]=='.'){ if(numbers.indexOf(".")!=-1){ throw new NotNumberException("表达式格式不正确:第"+(i+1)+"个字符'"+chars[i]+"'出现错误,数字中出现两个小数点。"); } if("".equals(numbers.toString())){ throw new WrongExpressException("表达式格式不正确:第"+(i+1)+"个字符'"+chars[i]+"'出现错误,小数点前必须有数字"); } numbers.append(chars[i]); }else if(isFlag(chars[i])){ if(isFlag(readchar)){ throw new WrongExpressException("表达式格式不正确,第"+(i+1)+"个字符'"+chars[i]+"'出现错误,计算符不能互相连接。"); } if(readchar!=')'){ result.add(numbers); numbers=new StringBuffer(); } if(operator.isEmpty()){ operator.push(chars[i]); }else{ Operator nowoper=operators.get(chars[i]); char popchar=operator.peek(); inner: while(1==1){ if(popchar=='('){ break inner; } if(nowoper.getPriority()>operators.get(popchar).getPriority()){ break inner; } popchar=operator.pop(); result.add(popchar); if(operator.isEmpty()){ break inner; } popchar=operator.peek(); } operator.push(chars[i]); } }else if(chars[i]=='('){ if(Validator.isNumber(String.valueOf(readchar))){ throw new WrongExpressException("表达式格式不正确:第"+(i+1)+"个字符'"+chars[i]+"'出现错误 括号不能与数字连接。"); } if(readchar==')'){ throw new WrongExpressException("表达式格式不正确:第"+(i+1)+"个字符'"+chars[i]+"'出现错误 异括号不能连接。"); } operator.push(chars[i]); }else if(chars[i]==')'){ if(readchar=='('){ throw new WrongExpressException("表达式格式不正确:第"+(i+1)+"个字符'"+chars[i]+"'出现错误 异括号不能连接。"); } if(isFlag(readchar)){ throw new WrongExpressException("表达式格式不正确:第"+(i+1)+"个字符'"+chars[i]+"'出现错误 括号不能与数字连接。"); } if(readchar!=')'){ result.add(numbers); numbers=new StringBuffer(); } char oper=operator.pop(); inner: while(1==1){ if(oper=='('){ break inner; } if(operator.isEmpty()){ throw new WrongExpressException("表达式格式不正确:第"+(i+1)+"个字符'"+chars[i]+"'出现错误 此闭括号没有追朔到开阔号。"); } result.add(oper); oper=operator.pop(); } }else{ throw new WrongExpressException("表达式格式不正确:第"+(i+1)+"个字符'"+chars[i]+"'出现错误 非法的表达式字符。"); } readchar=chars[i]; } if(readchar!=')') result.add(numbers); while(1==1){ if(operator.isEmpty()){ break; } char oper=operator.pop(); if(oper=='('){ throw new WrongExpressException("表达式格式不正确:发现有括号没有闭合。"); } result.add( oper); } return result; } //将后缀表达式计算出结果。 private double operator(List<Object> suffixExpression) throws WrongExpressException{ Stack<String> operator=new Stack<String>(); for (int i = 0; i < suffixExpression.size(); i++) { Object obj=suffixExpression.get(i); if(obj instanceof StringBuffer){ operator.push(obj.toString()); }else if(obj instanceof Character){ Operator oper=operators.get(obj); if(operator.isEmpty()){ throw new WrongExpressException("表达式格式不正确:发现多余操作符"); } double y=Double.parseDouble(operator.pop()); if(operator.isEmpty()){ throw new WrongExpressException("表达式格式不正确:发现多余操作符"); } double x=Double.parseDouble(operator.pop()); operator.push(String.valueOf(oper.getDouble(x, y))); }else{ throw new WrongExpressException("表达式格式不正确:出现不兼容处理类型"); } } double result=Double.parseDouble(operator.pop()); if(!operator.isEmpty()){ throw new WrongExpressException("表达式格式不正确:操作符不足"); } return result; } public String parseFunExpress(String express) throws WrongExpressException{ Matcher m=functionregex.matcher(express); while(m.find()){ double res=this.execfunction(express.substring(m.start(),m.end())); String resstr=""; if(res<0){ resstr="(0"+String.valueOf(res)+")"; }else{ resstr=String.valueOf(res); } express=m.replaceFirst("$1 "+resstr+"$9"); m=functionregex.matcher(express); } return express.replace(" ", ""); } public double execfunction(String funstr) throws WrongExpressException{ Matcher m=functionregex.matcher(funstr); if(!m.matches()){ throw new WrongExpressException("函数格式不正确"); } String[]param=m.replaceAll("$5").split(","); double []paramdouble=new double[param.length]; for (int i = 0; i < paramdouble.length; i++) { paramdouble[i]=Double.parseDouble(param[i]); } Function f=this.functions.get(m.replaceAll("$4")); if(f==null){ throw new WrongExpressException("未找到对应函数"); } return f.execute(paramdouble); } }
在此其中,我自定义了一些异常,通常自定义一些异常来抛出不正确的输入给用户程序,让错误的程序不会继续执行,并提示客户程序有效的错误信息是很必要的,在此要说明,尽量不要抛出jdk的内置异常,这些异常并没有做什么特殊设计,完全可以自己实现。
这些代码中我初步实现了一些支持函数的代码,但函数并不支持表达式的嵌套运算,因为此示例仅供学习使用,因此也未做任何性能上的处理,只是简单的算法实现。关于函数接口的代码如下,同时提供一个简单的实现:
package org.spiniper.commons.tool.expression.function; /** * 运算函数接口 * @author spiniper * */ public interface Function { /** * 执行函数体 * @param params 函数参数 * @return 执行结果 */ public double execute(double[] params); /** * 获得函数名称 * @return 函数名称 */ public String getName(); /** * 参数个数 * @return 参数个数 */ public int paramNum(); }
实现:
package org.spiniper.commons.tool.expression.function.commons; import org.spiniper.commons.tool.expression.function.Function; public class Power implements Function { public double execute(double[] params) { if(params.length<2){ return 0; } return Math.pow(params[0], params[1]); } public String getName() { // TODO Auto-generated method stub return "power"; } public int paramNum() { // TODO Auto-generated method stub return 2; } }
相关推荐
在计算机科学中,数据...总结来说,中缀表达式转后缀表达式是数据结构和算法中的一个经典问题,主要利用堆栈数据结构和运算符优先级规则。在C++中实现这一转换,可以提高计算效率,便于计算机解析和执行数学表达式。
一个简单的算法,利用栈实现中缀表达式与后缀表达式的转换
中缀表达式转化为后缀表达式算法及后缀表达式算法的实现 中缀表达式转化为后缀表达式算法是计算机科学中的一种常见算法,用于将中缀表达式转化为后缀表达式。该算法广泛应用于编译器、解释器、计算器等领域。 中缀...
按照惯例,算术表达式一般都写成中缀形式,即运算符总是出现在两个操作数之间,单目运算符除外),称为中缀表达式.编译系统对中缀表达式的处理方法是先把它转换为后后缀表达式.在后缀表达式中,运算符位于两个操作数的后面...
4. 中缀转后缀:使用栈来实现中缀表达式到后缀表达式的转换。 5. 表达式求值:根据后缀表达式,使用栈进行计算。 6. 输出结果:显示转换后的后缀表达式和计算后的值。 具体实现细节如下: 1. 定义结构体 `struct ...
本项目的目标是实现一个C++程序,它能够接收用户输入的中缀表达式,将其转换为后缀表达式,并计算出结果。这个过程涉及到以下几个关键知识点: 1. **优先级与结合性**:运算符有不同的优先级和结合性,例如乘法和除...
通过以上介绍,我们可以看到从理论到实践,从算法设计到具体实现,中缀转后缀的过程是计算机科学中的一个经典问题。掌握这一技能不仅有助于加深对表达式处理的理解,也为后续学习更高级的编译原理打下坚实的基础。...
中缀表达式转后缀表达式的算法通常涉及两个主要步骤:构建二叉表达式树(也称运算符优先树)和遍历该树以生成后缀表达式。下面将详细介绍这两个步骤以及C++代码实现的关键点。 1. **构建二叉表达式树**: - 二叉...
首先,中缀表达式转后缀表达式的算法通常使用栈数据结构实现。步骤如下: 1. 初始化一个空栈。 2. 从左到右扫描中缀表达式: - 如果当前字符是操作数,直接输出。 - 如果当前字符是运算符,比较它与栈顶运算符的...
在项目“101544246张绪鹏--中缀转换为后缀表达式及其计算”中,开发者可能实现了一个完整的Java程序,该程序能够读取用户输入的中缀表达式,将其转换为后缀表达式,并根据后缀表达式计算出结果。程序可能包含以下几...
目前该转换算法只支持数字在0至9之间的+-*/四元运算转换.*/ /**************程序员信息 ***************东北大学*******************东大很厉害**************** ***************软件学院*******************软件...
### Java 实现中缀表达式转换为后缀表达式及计算 #### 概述 在计算机科学中,表达式的表示形式通常有三种:前缀、中缀和后缀。其中,中缀表达式是我们日常书写数学表达式时最常见的形式,如`2 + 3 * 4`。然而,在...
主要步骤包括读取中缀表达式,处理每个字符,维护一个运算符栈,并生成后缀表达式。同时,可以利用表达式树的概念辅助理解和优化代码。 总结来说,中缀表达式转后缀表达式是编译原理中的基本操作,它涉及到了栈数据...
中缀表达式转换为后缀表达式_C++程序设计 ...中缀表达式转换为后缀表达式是一个常见的数据结构和算法问题,本文介绍了转换规则和算法实现,并使用 C++ 编程语言实现了中缀表达式到后缀表达式的转换。
中缀表达式、后缀表达式以及它们之间的转换是计算机科学中的一个重要概念,尤其是在编译原理和算法设计中。中缀表达式是我们日常数学运算中常见的形式,如 "2 + 3 * 4",而后缀表达式,也称为逆波兰表示法,将操作符...
最后,编写一个主函数,输入中缀表达式字符串,按照上述逻辑处理每个字符,得到后缀表达式字符串。 例如,对于中缀表达式 "2 + 3 * 4",其转换过程如下: 1. 遇到数字2,放入输出队列。 2. 遇到数字3,同样放入输出...
在本文中,我们将讨论...总的来说,这个课程设计提供了一个实际应用堆栈数据结构的例子,通过堆栈实现了中缀表达式到后缀表达式的转换以及后缀表达式的计算,展示了如何在程序设计中有效地处理数学表达式的求值问题。
总之,中缀表达式转后缀表达式是一个基础但重要的编程任务,它涉及到了数据结构、算法和计算理论。MATLAB作为一种强大的数值计算工具,提供了方便的接口和丰富的内置函数,使得这个任务的实现变得更加容易。通过这个...