这篇文章讲述的是算法趣味分数部分的表达式求值j问题的java实现,参考的书籍为清华大学出版社出版,贾蓓等编著的《c语言趣味编程1000例》,如有错误或者不当之处,还望各位大神批评指正。

问题描述

表达式求值,给出一个表达式包括 + - * / ( )等元素,数字类型包括整型和浮点型,写一个程序计算它的值

算法分析

  • 四则运算的规则是:先乘除后加减,右括号先算括号里的
  • 需要解决的问题有
  1. 把字符串中的每个字符(数字或运算符)提取出来
  2. 运算符之间比较优先级
  3. 使用两个站存放数字和运算符
  4. 数制的转换
  • 执行逻辑如下
/*循环结束条件为当前元素为结束符#,且运算符栈只剩# */
while(elem.charAt(0) != '#' || !operStack.getTop().equals('#')){
            /*若为数字则入栈*/
            if(isNumber(elem)){ 
                //入栈
            }else{
                /*获取当前运算符和栈顶的运算符判断优先级*/ 
                char prior = getPriority(oper1 , oper2) ;
                switch(prior){
                    case'<':{                   
                        //若当前元素优先级较低则入栈
                    }break ;
                    case'=':{   
                        //若优先级相等则说明是括号脱括号并判断下一个                                     
                    }
                    case'>':{
                        //若栈顶元素优先级较高则取出数字栈中的元素进行计算
                    }
                }
            }

代码实现

要实现这样复杂的操作要将问题分解:1. 编写处理字符串的类 ;2. 处理运算符之间的关系;3. 使用栈来处理核心结构

第一步:编写字符串处理函数

实现将表达式的每个元素都单独拿出来

/*字符串处理类,用于从字符串中分隔操作符*/
class StringHandler{
    /*要处理的字符串*/
    String STR ;
    /*分隔符,默认为空格*/
    char TOCKEN = ' ' ;

    /*遍历整个字符串的指针*/
    int INDEX = 0 ;

    public StringHandler(String str) {
        this.STR = str ;
    }
    public StringHandler(String str , char tocken) {
        this.STR = str ;
        this.TOCKEN = tocken ;
    }

    /**
     * @explain nextTocken方法: 返回当前分隔符到下一个分隔符之间的字符
     * @return String 分隔符之间的字符串
     * @throws 
     * @author 叶清逸
     * @date 2018年8月2日 下午10:31:36
     */
    public String nextElement(){
        /*如果指针越界返回空*/
        if(INDEX >= STR.length()){
            return null ;
        }
        /*跳过分隔符*/
        while(STR.charAt(INDEX) == TOCKEN){
            INDEX++ ;
        }
        String elem = "" ;
        /*若当前字符不为分隔符,则放入elem的缓存中*/
        while(INDEX<STR.length() && STR.charAt(INDEX) != TOCKEN){
            elem = elem + STR.charAt(INDEX) ;
            INDEX++ ;
        }
        INDEX++ ;
        return elem ;
    }
    /**
     * @explain hasMoreTocken方法: 判断是否还有分隔符
     * @return boolean 如果还有分隔符则返回true,否则返回false
     * @throws 
     * @author 叶清逸
     * @date 2018年8月2日 下午10:32:55
     */
    public boolean hasMoreElement(){
        /*如果指针越界返回空*/
        if(INDEX >= STR.length()){
            return false ;
        }
        /*跳过分隔符*/
        while(STR.charAt(INDEX) == TOCKEN){
            INDEX++ ;
        }
        String elem = "" ;
        int i = INDEX ;
        /*若当前字符不为分隔符,则放入elem的缓存中*/
        while(i<STR.length() && STR.charAt(i) != TOCKEN){
            elem = elem + STR.charAt(i) ;
            i++ ;
        }
        if(elem == ""){
            return false ;
        }else{
            return true;
        }
    }
}

第二步:处理运算符之间的优先级

  • 要实现比较运算符的优先级比较需要将符号索引化:0表示’+’,1表示’-‘,2表示’*’,3表示’/’,4表示’(‘,5表示’)’,6表示’#’
/**
 *@explain getIndex方法: 获取运算符的索引代码,0表示'+',1表示'-',2表示'*',3表示'/',4表示'(',5表示')',6表示'#'
 * @param oper 运算符
 * @return int 运算符的索引
 * @throws 
 * @author 叶清逸
 * @date 2018年8月3日 下午5:24:07
 */
private static int getIndex(char oper){
    int index ;
    switch(oper){
        case'+':{
            index = 0 ;
        } break ;
        case'-':{
            index = 1 ;
        } break ;
        case'*':{
            index = 2 ;
        } break ;
        case'/':{
            index = 3 ;
        } break ;
        case'(':{
            index = 4 ;
        } break ;
        case')':{
            index = 5 ;
        } break ;
        case'#':{
            index = 6 ;
        } break ;
        default:{
            index = -1 ;
        }
    };
    return index ;
}
  • 再根据索引获取两个运算符的优先级考虑使用二维数组
/**
 * @explain getPriority方法: 判断连个运算符的优先级
 * @param oper1 运算符1
 * @param oper2 运算符2
 * @return char 运算符的优先级,
 *              若为'<'则表示运算符1的优先级小于运算符2,
 *              若为'>'则表示运算符1的优先级大于运算符2,
 *              若为'='则表示则表示两个括号相遇
 *              若为' '即空格则表示优先级不存在
 * @throws 
 * @author 叶清逸
 * @date 2018年8月3日 下午5:01:05
 */
private static char getPriority(char oper1 , char oper2){
    /*获取两个运算符的索引*/
    int index1 = getIndex(oper1) ;
    int index2 = getIndex(oper2) ;
    /*使用二维数组保存优先级列表*/
    char [][] prioritys = { {'>','>','<','<','<','>','>'} ,
                            {'>','>','<','<','<','>','>'} ,
                            {'>','>','>','>','<','>','>'} ,
                            {'>','>','>','>','<','>','>'} ,
                            {'<','<','<','<','<','=',' '} ,
                            {'>','>','>','>',' ','>','>'} ,
                            {'<','<','<','<','<','=',' '} , } ;
    return prioritys[index1][index2];
}

第三步:判断一个元素是数字还是运算符

/**
 * @explain isNumber方法: 判断传入元素是数字还是运算符
 * @param elem 传入元素
 * @return boolean 若为元素则返回true,若为运算符则返回false
 * @throws 
 * @author 叶清逸
 * @date 2018年8月3日 下午8:03:06
 */
static boolean isNumber(String elem){
    /*若为单个字符则判断是否为单独的加减*/
    if(elem.length() == 1){
        if(elem.charAt(0) == '+' || elem.charAt(0) == '-'){
            return false ;
        }
    }
    /*判断每个字符是否为数字*/
    for(int i=0 ; i<elem.length() ; i++){
        char c = elem.charAt(i) ;
        if( !(c == '+' || c == '-' || c >= '0' && c <= '9' || c == '.') ){
            return false ;
        }
    }
    return true ;
}

第四步:编写运算方法

功能是传入两个数和这两个数的运算符,返回运算结果

/**
 * @explain calculate方法: 运算操作
 * @param oper 运算符
 * @param num1 数字1
 * @param num2 数字2
 * @return double 返回运算的结果
 * @throws 
 * @author 叶清逸
 * @date 2018年8月3日 下午11:34:25
 */
private static double calculate(char oper , double num1 , double num2){
    double result = 0 ;

    switch(oper){
        case'+':{
            result = num1 + num2 ;
        }break ;
        case'-':{
            result = num1 - num2 ;
        }break ;
        case'*':{
            result = num1 * num2 ;
        }break ;
        case'/':{
            result = num1 / num2 ;
        }break ;
    }

    return result ;
}

第五步:编写核心代码

/**
 * @explain evaluate方法: 计算表达式的值
 * @param exp 表达式
 * @return double 表达式的运算结果
 * @throws 
 * @author 叶清逸
 * @date 2018年8月4日 下午8:58:32
 */
private static double evaluate(String exp){
    /*定义两个辅助栈,一个存放数字,一个存放运算符*/
    Stack numStack = new LinkStack() ;
    numStack.init();
    Stack operStack = new LinkStack() ;
    operStack.init() ;
    /*初始化用于处理字符串的类*/
    StringHandler sh = new StringHandler(exp) ;
    /*遍历整个表达式,把字符放入对应的栈*/
    operStack.push('#');
    String elem = sh.nextElement() ;

    while(elem.charAt(0) != '#' || !operStack.getTop().equals('#')){

        if(isNumber(elem)){                                         //若为数字压入栈
            numStack.push(elem);
            elem = sh.nextElement() ;
        }else{
            /*获取当前运算符和栈顶的运算符*/
            char oper1 = operStack.getTop().toString().charAt(0);   //栈顶运算符
            char oper2 = elem.charAt(0) ;                           //当前运算符 

            char prior = getPriority(oper1 , oper2) ;
            switch(prior){
                case'<':{                                           //若当前元素优先级较低则入栈
                    operStack.push(elem);
                    elem = sh.nextElement();
                }break ;
                case'=':{                                           //若优先级相等则说明是括号脱括号并判断下一个
                    operStack.pop() ;
                    elem = sh.nextElement();
                    continue ;
                }
                case'>':{                                           //若栈顶元素优先级较高则进行计算
                    double num2 = Double.parseDouble(numStack.pop().toString()) ;
                    double num1 = Double.parseDouble(numStack.pop().toString()) ;
                    char operator = operStack.pop().toString().charAt(0) ;

                    double result = calculate(operator, num1, num2) ;

                    numStack.push(result);
                }
            }
        }
    }
    /*运算最后数字栈中剩余的数字即为运算结果*/
    double result = Double.parseDouble(numStack.pop().toString()) ;
    /*返回表达式运算结果*/
    return result ;
}

完整代码

package stack_question;

import stack.LinkStack;
import stack.Stack;

/**
 * @author 叶清逸
 * @date 2018年8月2日下午9:37:37
 * @version 1.0
 * @project stack_question
 */
public class Q3_ExpressionEvaluate {
    /**
     * 问题描述:表达式求值,给出一个表达式包括 + - * / ( )等元素,写一个程序计算它的值
     * 
     * 算法分析:1. 把字符串中的每个字符(数字或运算符)提取出来
     *          2. 运算符之间比较优先级
     *          3. 使用两个栈来存放数字和运算符
     *          4. 数制之间的转换
     * 
     * 复杂度分析:时间复杂度:O(n) , 空间复杂度:O(n)
     */
    public static void main(String[] args) {
        /*初始化表达式*/
        String exp = "4 * ( -10 + 2.5 ) + 1 #" ;
        /*进行表达式运算*/
        double result = evaluate(exp) ;

        System.out.println(exp + " = " + result);

    }
    /**
     * @explain evaluate方法: 计算表达式的值
     * @param exp 表达式
     * @return double 表达式的运算结果
     * @throws 
     * @author 叶清逸
     * @date 2018年8月4日 下午8:58:32
     */
    private static double evaluate(String exp){
        /*定义两个辅助栈,一个存放数字,一个存放运算符*/
        Stack numStack = new LinkStack() ;
        numStack.init();
        Stack operStack = new LinkStack() ;
        operStack.init() ;
        /*初始化用于处理字符串的类*/
        StringHandler sh = new StringHandler(exp) ;
        /*遍历整个表达式,把字符放入对应的栈*/
        operStack.push('#');
        String elem = sh.nextElement() ;

        while(elem.charAt(0) != '#' || !operStack.getTop().equals('#')){

            if(isNumber(elem)){                                         //若为数字压入栈
                numStack.push(elem);
                elem = sh.nextElement() ;
            }else{
                /*获取当前运算符和栈顶的运算符*/
                char oper1 = operStack.getTop().toString().charAt(0);   //栈顶运算符
                char oper2 = elem.charAt(0) ;                           //当前运算符 

                char prior = getPriority(oper1 , oper2) ;
                switch(prior){
                    case'<':{                                           //若当前元素优先级较低则入栈
                        operStack.push(elem);
                        elem = sh.nextElement();
                    }break ;
                    case'=':{                                           //若优先级相等则说明是括号脱括号并判断下一个
                        operStack.pop() ;
                        elem = sh.nextElement();
                        continue ;
                    }
                    case'>':{                                           //若栈顶元素优先级较高则进行计算
                        double num2 = Double.parseDouble(numStack.pop().toString()) ;
                        double num1 = Double.parseDouble(numStack.pop().toString()) ;
                        char operator = operStack.pop().toString().charAt(0) ;

                        double result = calculate(operator, num1, num2) ;

                        numStack.push(result);
                    }
                }
            }
        }
        /*运算最后数字栈中剩余的数字即为运算结果*/
        double result = Double.parseDouble(numStack.pop().toString()) ;
        /*返回表达式运算结果*/
        return result ;
    }
    /**
     * @explain getPriority方法: 判断连个运算符的优先级
     * @param oper1 运算符1
     * @param oper2 运算符2
     * @return char 运算符的优先级,
     *              若为'<'则表示运算符1的优先级小于运算符2,
     *              若为'>'则表示运算符1的优先级大于运算符2,
     *              若为'='则表示则表示两个括号相遇
     *              若为' '即空格则表示优先级不存在
     * @throws 
     * @author 叶清逸
     * @date 2018年8月3日 下午5:01:05
     */
    private static char getPriority(char oper1 , char oper2){
        /*获取两个运算符的索引*/
        int index1 = getIndex(oper1) ;
        int index2 = getIndex(oper2) ;
        /*使用二维数组保存优先级列表*/
        char [][] prioritys = { {'>','>','<','<','<','>','>'} ,
                                {'>','>','<','<','<','>','>'} ,
                                {'>','>','>','>','<','>','>'} ,
                                {'>','>','>','>','<','>','>'} ,
                                {'<','<','<','<','<','=',' '} ,
                                {'>','>','>','>',' ','>','>'} ,
                                {'<','<','<','<','<','=',' '} , } ;
        return prioritys[index1][index2];
    }
    /**
     * @explain getIndex方法: 获取运算符的索引代码,0表示'+',1表示'-',2表示'*',3表示'/',4表示'(',5表示')',6表示'#'
     * @param oper 运算符
     * @return int 运算符的索引
     * @throws 
     * @author 叶清逸
     * @date 2018年8月3日 下午5:24:07
     */
    private static int getIndex(char oper){
        int index ;
        switch(oper){
            case'+':{
                index = 0 ;
            } break ;
            case'-':{
                index = 1 ;
            } break ;
            case'*':{
                index = 2 ;
            } break ;
            case'/':{
                index = 3 ;
            } break ;
            case'(':{
                index = 4 ;
            } break ;
            case')':{
                index = 5 ;
            } break ;
            case'#':{
                index = 6 ;
            } break ;
            default:{
                index = -1 ;
            }
        };
        return index ;
    }
    /**
     * @explain isNumber方法: 判断传入元素是数字还是运算符
     * @param elem 传入元素
     * @return boolean 若为元素则返回true,若为运算符则返回false
     * @throws 
     * @author 叶清逸
     * @date 2018年8月3日 下午8:03:06
     */
    static boolean isNumber(String elem){
        /*若为单个字符则判断是否为单独的加减*/
        if(elem.length() == 1){
            if(elem.charAt(0) == '+' || elem.charAt(0) == '-'){
                return false ;
            }
        }
        /*判断每个字符是否为数字*/
        for(int i=0 ; i<elem.length() ; i++){
            char c = elem.charAt(i) ;
            if( !(c == '+' || c == '-' || c >= '0' && c <= '9' || c == '.') ){
                return false ;
            }
        }
        return true ;
    }
    /**
     * @explain calculate方法: 运算操作
     * @param oper 运算符
     * @param num1 数字1
     * @param num2 数字2
     * @return double 返回运算的结果
     * @throws 
     * @author 叶清逸
     * @date 2018年8月3日 下午11:34:25
     */
    private static double calculate(char oper , double num1 , double num2){
        double result = 0 ;

        switch(oper){
            case'+':{
                result = num1 + num2 ;
            }break ;
            case'-':{
                result = num1 - num2 ;
            }break ;
            case'*':{
                result = num1 * num2 ;
            }break ;
            case'/':{
                result = num1 / num2 ;
            }break ;
        }

        return result ;
    }
}
/*字符串处理类,用于从字符串中分隔操作符*/
class StringHandler{
    /*要处理的字符串*/
    String STR ;
    /*分隔符,默认为空格*/
    char TOCKEN = ' ' ;

    /*遍历整个字符串的指针*/
    int INDEX = 0 ;

    public StringHandler(String str) {
        this.STR = str ;
    }
    public StringHandler(String str , char tocken) {
        this.STR = str ;
        this.TOCKEN = tocken ;
    }

    /**
     * @explain nextTocken方法: 返回当前分隔符到下一个分隔符之间的字符
     * @return String 分隔符之间的字符串
     * @throws 
     * @author 叶清逸
     * @date 2018年8月2日 下午10:31:36
     */
    public String nextElement(){
        /*如果指针越界返回空*/
        if(INDEX >= STR.length()){
            return null ;
        }
        /*跳过分隔符*/
        while(STR.charAt(INDEX) == TOCKEN){
            INDEX++ ;
        }
        String elem = "" ;
        /*若当前字符不为分隔符,则放入elem的缓存中*/
        while(INDEX<STR.length() && STR.charAt(INDEX) != TOCKEN){
            elem = elem + STR.charAt(INDEX) ;
            INDEX++ ;
        }
        INDEX++ ;
        return elem ;
    }
    /**
     * @explain hasMoreTocken方法: 判断是否还有分隔符
     * @return boolean 如果还有分隔符则返回true,否则返回false
     * @throws 
     * @author 叶清逸
     * @date 2018年8月2日 下午10:32:55
     */
    public boolean hasMoreElement(){
        /*如果指针越界返回空*/
        if(INDEX >= STR.length()){
            return false ;
        }
        /*跳过分隔符*/
        while(STR.charAt(INDEX) == TOCKEN){
            INDEX++ ;
        }
        String elem = "" ;
        int i = INDEX ;
        /*若当前字符不为分隔符,则放入elem的缓存中*/
        while(i<STR.length() && STR.charAt(i) != TOCKEN){
            elem = elem + STR.charAt(i) ;
            i++ ;
        }
        if(elem == ""){
            return false ;
        }else{
            return true;
        }
    }
}

样例输出

初始串:”4 * ( -10 + 2.5 ) + 1 #”

4 * ( -10 + 2.5 ) + 1 # = -29.0