Entry类 这个类对表达式的合法性进行了粗筛:

package com.hy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

// 此类用于把算术表达式送入解析器
public class Entry {
    public static void main(String[] args) throws IOException{
        // 取得用户输入的表达式
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String rawExpression = null;
        System.out.print("请输入算术表达式:");
        rawExpression = br.readLine(); 

        // 得到合法的算术表达式
        String expression="";
        for(int i=0;i<rawExpression.length();i++){
            // 拿到表达式的每个字符
            char c=rawExpression.charAt(i);
            //System.out.print(c+","); 

            if(Character.isDigit(c) || c=='+' || c=='-' || c=='*' || c=='/' || c=='(' || c==')' || c=='.'){
                //System.out.print(c);
                expression+=c;
            }else{
                System.out.print(" "+c+"不是合法的算术表达式字符.");
                System.exit(0);
            }
        }

        // 送去解析
        Lexer p=new Lexer(expression);
        //p.print();

        //
        Tree t=new Tree(p.getInfixList());
        try {
            System.out.println(t.evaluate());
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

执行结果 以下测试用例都通过了:

请输入算术表达式:1+2+3
6.0

请输入算术表达式:1+2*3
7.0

请输入算术表达式:2*(3+4)
14.0

请输入算术表达式:1+2*(6-4)
5.0

请输入算术表达式:1+2*(5-4)+6-7
2.0

请输入算术表达式:(1+2)*3-4*(6-5)
5.0

请输入算术表达式:(1+2)*(3+4)
21.0

请输入算术表达式:1.1*5+(3+4)*10
75.5

Lexer类 这个类起词法分析器的作用,其核心利器是正则表达式,分词完毕后得到一个含有中序表达式的列表,如 ”1.2,+,3,*,4“:

package com.hy;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

// 此类用于将算术表达式解析成包含操作数和操作符的链表,扮演分词器的角色
public class Lexer {
    private List<String> list;// 用于存储中序表达式的链表

    public List<String> getInfixList() {
        return list;
    }

    public Lexer(String expression){
        list=new ArrayList<String>();

        // 使用正则表达式分词
        String regExp = "(\\d+(\\.*)\\d*)|(\\+)|(\\-)|(\\*)|(\\/)|(\\()|(\\))";

        Pattern pattern=Pattern.compile(regExp);
        Matcher matcher=pattern.matcher(expression);
        while(matcher.find()){
            list.add(matcher.group(0));
        }
    }

    public void print(){
        for(String str:list){
            System.out.println(str);
        }
    }
}

Tree类 输入一个中序表达式列表,得到构建好的树,这棵树就是算术表达式的语法树。递归的build函数是本类代码的核心:

package com.hy;

import java.util.List;

// 以算术表达式为基础构建一棵二叉树
public class Tree {
    // 根节点
    private Node root;

    // infixList为分词完毕的中序表达式列表
    public Tree(List<String> infixList){
        root=build(infixList,0,infixList.size());
    }

    // 构建一棵树,从根节点构建起
    private Node build(List<String> list,int start,int end){
        int depth=0;//记录深度,进一层括号加一,退出来减一
        int plusDivideRightmostPos=-1;// 记录最右边的加减号位置
        int multiDivideRightmostPos=-1;// 记录最右边的乘除号位置

        // 操作数
        if(start==end-1){
            // 下标相差一,说明找到的是没有子节点的叶子节点,也即操作数节点
            Node leafNode=new Node(NodeType.Digit,list.get(start));
            return leafNode;
        }

        // 这个循环是为了找括号外最右边的运算符位置
        for(int i=start;i<end;i++){
            String operatorText=list.get(i);// 获得操作符的文字,如果是操作数直接ignore

            if(operatorText.equals("(")){
                depth++;
            }else if(operatorText.equals(")")){
                depth--;
            }else if(operatorText.equals("+") || operatorText.equals("-") ){
                if(depth==0){
                    plusDivideRightmostPos=i;
                }
            }else if(operatorText.equals("*") || operatorText.equals("/") ){
                if(depth==0){
                    multiDivideRightmostPos=i;
                }
            }
        }

        int rightMost=-1;

        if(plusDivideRightmostPos==-1 && multiDivideRightmostPos==-1){
            // 整个算式被多余的括号括起来了,去掉这层多余的括号再做
            return build(list,start+1,end-1);
        }

        // 优先取加减号的位置,因为它的计算优先级最低,应当最后算
        rightMost=plusDivideRightmostPos;

        if(plusDivideRightmostPos==-1 && multiDivideRightmostPos>0){
            // 括号外只有乘除号,如(1+2)*(3+4),这时只有取乘除号位置,
            rightMost=multiDivideRightmostPos;
        }

        // 如果能走到这里,则最右边括号外的运算符位置已经找到了,可以开始构建节点
        String operator=list.get(rightMost);
        Node nodeOper=new Node(operator);// 这里创建的节点都是操作符节点,不是最终的叶子节点

        // 以最右边的操作符为界,分两侧构建左右子节点
        nodeOper.setLeftNode(build(list,start,rightMost));
        nodeOper.setRightNode(build(list,rightMost+1,end));

        // 返回构建完的节点
        return nodeOper;
    }

    // 取二叉树的值
    public float evaluate() throws Exception{
        return this.root.getValue();
    }
}

Node类 这是如教科书似的二叉树定义:

package com.hy;

// 二叉树节点类
public class Node {
    private NodeType type;
    private float value;
    private Node leftNode;// 左节点
    private Node rightNode;// 右节点

    public Node(){
        type=NodeType.Undifined;
        value=0.0f;
        leftNode=null;
        rightNode=null;
    }

    public Node(String nodeTypeText){
        if(nodeTypeText.equals("+")){
            this.type=NodeType.OP_Plus;
        }else if(nodeTypeText.equals("-")){
            this.type=NodeType.OP_Minus;
        }else if(nodeTypeText.equals("*")){
            this.type=NodeType.OP_Multi;
        }else if(nodeTypeText.equals("/")){
            this.type=NodeType.OP_Divide;
        }else{
            this.type=NodeType.Undifined;
        }

        value=0.0f;
        leftNode=null;
        rightNode=null;
    }

    public Node(NodeType type){
        this.type=type;
        value=0.0f;
        leftNode=null;
        rightNode=null;
    }

    public Node(NodeType type,String str){
        this.type=type;
        this.value=Float.valueOf(str);
        leftNode=null;
        rightNode=null;
    }

    public Node(NodeType type,float value,Node leftNode,Node rightNode){
        this.type=type;
        this.value=value;
        this.leftNode=leftNode;
        this.rightNode=rightNode;
    }

    public Node(NodeType type,Node leftNode,Node rightNode){
        this.type=type;
        this.value=0;
        this.leftNode=leftNode;
        this.rightNode=rightNode;
    }

    public float getValue() throws Exception{
        if(this.type==NodeType.Digit){
            return value;
        }else if(this.type==NodeType.OP_Divide){
            return leftNode.getValue()/rightNode.getValue();
        }else if(this.type==NodeType.OP_Minus){
            return leftNode.getValue()-rightNode.getValue();
        }else if(this.type==NodeType.OP_Multi){
            return leftNode.getValue()*rightNode.getValue();
        }else if(this.type==NodeType.OP_Plus){
            return leftNode.getValue()+rightNode.getValue();
        }else{
            throw new Exception("Not initialize");
        }
    }

    public void setLeftNode(Node leftNode) {
        this.leftNode = leftNode;
    }

    public void setRightNode(Node rightNode) {
        this.rightNode = rightNode;
    }

    public Node getLeftNode() {
        return leftNode;
    }

    public Node getRightNode() {
        return rightNode;
    }

    public String toString(){
        if(this.type==NodeType.Digit){
            return String.valueOf(value)+" ";
        }else if(this.type==NodeType.OP_Divide){
            return "/ ";
        }else if(this.type==NodeType.OP_Minus){
            return "- ";
        }else if(this.type==NodeType.OP_Multi){
            return "* ";
        }else if(this.type==NodeType.OP_Plus){
            return "+ ";
        }else{
            return "? ";
        }
    }

    public NodeType getType() {
        return type;
    }

    public void setType(NodeType type) {
        this.type = type;
    }

    public void setValue(float value) {
        this.value = value;
    }
}

NodeType枚举

package com.hy;

// 节点类型
public enum NodeType {
    Undifined,
    OP_Plus,
    OP_Minus,
    OP_Multi,
    OP_Divide,
    Digit,
}

到这里,将算术表达式求值的两种方式--转后序表达式或二叉树求值都已经实现过了,虽然其词法分析器和语法分析器还稚嫩,但万丈高楼总需平地起。

参考文献:

1.算术表达式构造二叉树; 二叉树计算算术表达式 https://blog.csdn.net/qq120848369/article/details/5673969

2.算数表达式--二叉树 https://www.cnblogs.com/gw811/archive/2012/10/12/2720777.html

--END-- 2019年9月4日11点08分

[Java]算术表达式求值之三(中序表达式转二叉树方案 支持小数)的更多相关文章

  1. 表达式求值(noip2015等价表达式)

    题目大意 给一个含字母a的表达式,求n个选项中表达式跟一开始那个等价的有哪些 做法 模拟一个多项式显然难以实现那么我们高兴的找一些素数代入表达式,再随便找一个素数做模表达式求值优先级表 - ( ) + ...

  2. [Java]算术表达式求值之二(中序表达式转后序表达式方案,支持小数)

    Inlet类,入口类,这个类的主要用途是验证用户输入的算术表达式: package com.hy; import java.io.BufferedReader; import java.io.IOEx ...

  3. [Java]算术表达式求值之一(中序表达式转后序表达式方案)

    第二版请见:https://www.cnblogs.com/xiandedanteng/p/11451359.html 入口类,这个类的主要用途是粗筛用户输入的算术表达式: package com.h ...

  4. java实现算术表达式求值

    需要根据配置的表达式(例如:5+12*(3+5)/7.0)计算出相应的结果,因此使用java中的栈利用后缀表达式的方式实现该工具类. 后缀表达式就是将操作符放在操作数的后面展示的方式,例如:3+2 后 ...

  5. [Java]将算术表达式(中序表达式Infix)转成后续表达式Postfix

    Inlet类: package com.hy; import java.io.BufferedReader; import java.io.IOException; import java.io.In ...

  6. C语言中缀表达式求值(综合)

    题前需要了解的:中缀.后缀表达式是什么?(不知道你们知不知道,反正我当时不知道,搜的百度) 基本思路:先把输入的中缀表达式→后缀表达式→进行计算得出结果 栈:"先进先出,先进后出" ...

  7. 【zzuli-1923】表达式求值

    题目描述 假设表达式定义为:1. 一个十进制的正整数 X 是一个表达式.2. 如果 X 和 Y 是 表达式,则 X+Y, X*Y 也是表达式; *优先级高于+.3. 如果 X 和 Y 是 表达式,则 ...

  8. 利用栈实现算术表达式求值(Java语言描述)

    利用栈实现算术表达式求值(Java语言描述) 算术表达式求值是栈的典型应用,自己写栈,实现Java栈算术表达式求值,涉及栈,编译原理方面的知识.声明:部分代码参考自茫茫大海的专栏. 链栈的实现: pa ...

  9. [Java]算术表达式组建二叉树,再由二叉树得到算式的后序和中序表达式

    Entry类: package com.hy; import java.io.BufferedReader; import java.io.IOException; import java.io.In ...

随机推荐

  1. easyui-datagrid 编辑模式详解——combobox

    用于列表显示号了,需要改动某一列的值,而且根据每一行的数据去加载data数据,放在这个列中供别人选择 //-------------------- 代码可变区//---------- 数据定义区var ...

  2. eclipse中svn从分支合并到主干及冲突解决

    https://blog.csdn.net/shengqianfeng/article/details/79203156

  3. js常用阻止冒泡事件

    原文链接:http://caibaojian.com/javascript-stoppropagation-preventdefault.html 防止冒泡 w3c的方法是e.stopPropagat ...

  4. linux常用命令(centos)

    linux 命令有很多,常用的很少. #######################系统相关############################ lsb_release -a 查看系统信息 cat ...

  5. Linux用户组管理及用户权限3

    用户.组管理命令 安全上下文:        进程以其发起者的身份运行:            进程对文件的访问权限,取决于发此进程的用户的权限 系统用户:为了能够让那些后台进程或服务类进程以非管理员 ...

  6. 吐槽一下jsoup

    网络爬虫的本质就是通过域名加上特定的路由方式与远程资源建立一个短暂的连接,然后通过io流的方式读取.然后说一下jsoup,jsoup可以说是目前的爬虫工具包里面对java底层的工具类封装最简单的一种了 ...

  7. 原生 JS实现一个简单分页插件

    最近做的一个 PC端的需求,这个需求中有一个小点,页面底部有一块列表区域,这个列表的数据量比较大,需要进行分页控制,切换页码的时候,发送一个 ajax请求,在页面无刷新的情况下,实现列表数据的刷新,所 ...

  8. web添加学生信息(首发web)

    程序思路,先在JSP上画好页面,然后再创建一Servlet文件用于判断在网页上操作是否正确,还需要与数据库相连接,用DBUtile文件连接数据库,用Dao层来实现数据的增加,用Service来服务于D ...

  9. MySQL 关于自定义函数的操作

    -- 函数 --> 模块化,封装,代码复用 create function 函数名([参数列表]) returns 数据类型 begin SQL语句: return 值: end; 示例: -- ...

  10. 如何利用fiddler4 抓取手机的数据包

    1.安装fiddler . 2.设置fiddler  .tool==> option里面  https 要打开,然后选择actions 第一个 安装本地证书: 3.设置手机访问的数据都要经过fi ...