栈实现综合计算器、逆波兰计算器

对于一个字符串类型的表达式,实现基础的加减乘除运算

String expression = "8/2+2*5-4";

使用栈来实现这个表达式的解析和运算

实现思路:

1,通过一个index来遍历我们的表达式

2,如果发现是一个数字,判断下一位是不是运算符,如果是,把数字入数栈。如果不是,继续扫描,拼接数字

3,如果发现是一个运算符,判断符号栈是否为空,如果为空,直接入符号栈。如果不为空,就跟符号栈中栈顶的符号进行比较,如果优先级小于等于栈顶的符号,就需要从数栈中pop出两个数,从符号栈中pop出一个符号进行运算(注意运算时候的顺序,在进行减和除的时候,后面出来的数在前),将得到的结果入数栈,将当前扫描到的符号入符号栈。如果当前符号的优先级大于符号栈中栈顶的符号,就直接把当前符号入符号栈。

4,当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,进行运算。

5,最后数栈中只有一个数,就是表达式的结果。

代码实现:

public class StackCompute {

    @Test
    public void main(){

        //表达式
        String expression = "8/2+2*5-4";
        //创建两个栈,一个数栈,一个符号栈
        Stack<Integer> numStack = new Stack<>();
        Stack<Character> operStack = new Stack<>();

        //定义需要的变量
        int index = 0;//用于扫描表达式
        int num1 = 0;
        int num2 = 0;
        int oper = 0;//符号
        int result = 0;//运算结果
        char ch = ' ';//将每次扫描到char保存到ch
        String keepNum = "";//拼接数

        //开始while循环扫描表达式
        while(true){
            //依次得到表达式中的每一个字符
            ch = expression.substring(index,index+1).charAt(0);
            //判断ch是什么,然后做相应的处理
            if(isOper(ch)){//如果是一个运算符
                //判断当前符号栈是否为空
                if(!operStack.isEmpty()){
                    /*
                        如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数
                        在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前扫描到的操作符入符号栈
                     */
                    if (priority(ch) <= priority(operStack.peek())) {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        result = cal(num1,num2,oper);
                        //把运算结果入数栈
                        numStack.push(result);
                        //当前符号入符号栈
                        operStack.push(ch);
                    }else{
                        //如果当前符号的优先级大于栈中符号的优先级,就直接入符号栈
                        operStack.push(ch);
                    }
                }else{
                    //如果为空,直接入栈
                    operStack.push(ch);
                }
            }else{
                //如果是数,入数栈
                //处理多位数
                keepNum += ch;
                //如果是表达式的最后一位。直接入数栈
                if(index == expression.length()-1){
                    numStack.push(Integer.parseInt(keepNum));
                }else{
                    //如果不是最后一位,则判断后一位是不是字符,如果是字符,则入栈,反之,则继续扫描
                    if(isOper(expression.substring(index+1,index+2).charAt(0))){
                        numStack.push(Integer.parseInt(keepNum));
                        keepNum = "";//清空keepNum
                    }
                }
            }
            index++;
            if(index >= expression.length()){
                break;
            }
        }

        //当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行。
        while(true){
            //如果符号栈为空,表示已经计算到最后的结果,数栈中此时应该只有一个数字,就是结果
            if(operStack.isEmpty()){
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            result = cal(num1,num2,oper);
            //把运算结果入数栈
            numStack.push(result);
        }

        System.out.println(numStack.pop());
    }

    //判断是否是一个运算符,目前仅限于加减乘除
    boolean isOper(int ch){
        return ch == '+' || ch == '-' || ch == '*' || ch == '/';
    }

    //判断优先级
    int priority(int ch){
        if(ch == '*' || ch == '/'){
            return 1;
        }else{
            return 0;
        }
    }

    //计算方法
    int cal(int num1,int num2,int oper){
        int result = 0;
        switch (oper){
            case '+': result = num1 + num2;break;
            case '-': result = num2 - num1;break;//注意顺序,后出来的数做被减数
            case '*': result = num1 * num2;break;
            case '/': result = num2 / num1;break;//注意顺序,后出来的数做被除数
        }

        return result;
    }
}

在计算机中,前缀表达式(波兰表达式)或者后缀表达式(逆波兰表达式)比中缀表达式更好解析。

前缀表达式

 后缀表达式

 后缀表达式的求值:

public class PolandNotation {

    public static void main(String[] args) {
        //定义一个逆波兰表达式
        //5*7-8+3+9/3  =>  5 7 * 8 - 3 + 9 3 / +
        //为了方便,我们吧逆波兰表达式的数字和符号之间使用空格隔开
        String suffixExpression = "5 7 * 8 - 3 + 9 3 / +";

        /**
         * 思路
         * 1,先将表达式放入一个ArrayList中
         * 2,遍历ArrayList配合栈完成计算
         */
        List<String> list = getListString(suffixExpression);
        System.out.println(calculate(list));
    }

    //将表达式转换成集合
    public static List<String> getListString(String suffixExpression) {
        List<String> list = new ArrayList<String>();
        String[] split = suffixExpression.split(" ");
        for (String ele : split) {
            list.add(ele);
        }
        return list;
    }

    /**
     * 完成对逆波兰表达式的计算
     * 思路分析:
     * 1,从左至右扫描,将3和4压入堆栈;
     * 2,遇到+运算符,因此弹出4和3 ( 4为栈顶元素, 3为次项元素),计算出3+4的值,得7,再将7入栈;
     * 3,将5入栈;
     * 4,接下来是*运算符,因此弹出5和7,计算出7*5=35,将35入栈;
     * 5,将6入栈;
     * 6,最后是-运算符,计算出35-6的值,即29,由此得出最终结果;
     *
     * @param ls
     * @return
     */
    public static int calculate(List<String> ls) {

        //创建一个栈
        Stack<String> stack = new Stack<>();

        for (String item : ls) {
            if (item.matches("\\d+")) {//如果是数字
                stack.push(item);//入栈
            } else {
                //pop出两个数进行运算后再入栈
                //因为需要用后面弹出的数做被减数或被除数,所以为了方便,这里第一个弹出的数命名为num2
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int result = 0;
                if (item.equals("+")) {
                    result = num1 + num2;
                } else if (item.equals("-")) {
                    result = num1 - num2;
                } else if (item.equals("/")) {
                    result = num1 / num2;
                } else if (item.equals("*")) {
                    result = num1 * num2;
                }else{
                    throw new RuntimeException("运算符有误");
                }
                //把结果入栈
                stack.push(result+"");
            }
        }
        //最后留在栈中的数就是运算结果
        return Integer.parseInt(stack.pop());
    }
}

那么问题来了,如何把一个中缀表达式转成一个后缀表达式呢?

思路分析:

1,初始化两个栈,运算符栈s1和存储中间结果栈s2

2,从左至右扫描中缀表达式

3,遇到数字时,将其压入s2

4,遇到运算符时,比较其与s1栈顶运算符的优先级

    4.1,如果s1为空,或栈顶运算符为左括号,则直接将此运算符入s1

    4.2,如果运算符比栈顶的运算符优先级高,也将运算符入s1

    4.3,如果运算符比栈顶的运算符优先级低,将s1的运算符弹出压入s2,再次转到4.1中与新的栈顶运算符比较

5,遇到括号时

    5.1,如果是左括号,直接压入s1

    5.2,如果是右括号,则依次弹出s1栈顶的运算符压入s2,直到遇到左括号为止,此时将这一对括号丢弃

6,重复步骤2至5,直到表达式的最右边

7,将s1中剩余的运算符依次弹出并压入s2,

8,依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

代码实现:

    public static void main(String[] args) {

        List<String> list = toInfixExpressionList("1+((2+3)*4)-5");
        System.out.println(list);
        
        List<String> result = parseSuffixExpreesionList(list);
        System.out.println(result);
    }


    /**
     * 将中缀表达式转换成对应的list
     *
     * @param s
     * @return
     */
    public static List<String> toInfixExpressionList(String s) {
        //定义一个list,存放中缀表达式对应的内容
        List<String> list = new ArrayList<>();
        int i = 0;//这是一个指针,用于遍历中缀表达式字符串
        String str;//对多位数的拼接
        char c;//每遍历到一个字符就放入到c中

        do {
            //如果c是一个非数字,加入到list中  0-9对应的Asall码是48-57
            if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
                list.add(c + "");
                i++;
            } else {//如果是一个数字
                str = "";
                while (i < s.length() && (c = s.charAt(i)) > 48 && (c = s.charAt(i)) < 57) {
                    str += c;//拼接多位数
                    i++;
                }
                list.add(str);
            }
        } while (i < s.length());

        return list;
    }

    /**
     * 中缀表达式的list转后缀表达式list
     *
     * @param list
     * @return
     */
    public static List<String> parseSuffixExpreesionList(List<String> list) {

        //定义两个栈
        Stack<String> s1 = new Stack<>();//符号栈

        /**
         * 因为s2这个栈,在整个转换过程中,没有pop操作,而且后面还要逆序输出
         * 就比较麻烦,所以使用ArrayList代替
         */
        List<String> s2 = new ArrayList<>();//存储中间结果的list

        //遍历list
        for (String item : list) {
            //如果是一个数字,加入s2
            if (item.matches("\\d+")) {
                s2.add(item);
            } else if (item.equals("(")) {
                s1.push(item);
            }else if(item.equals(")")){
                //如果是右括号,则依次弹出s1栈顶的运算符压入s2,直到遇到左括号为止,此时将这一对括号丢弃
                while(!s1.peek().equals("(")){
                    s2.add(s1.pop());
                }
                s1.pop();//将左括号弹出,消除一堆小括号
            }else{
                //如果运算符比栈顶的运算符优先级低,将s1的运算符弹出压入s2,再次转到4.1中与新的栈顶运算符比较
                while(s1.size() != 0 && getValue(s1.peek()) >= getValue(item)){
                    s2.add(s1.pop());
                }
                //然后将item入栈
                s1.push(item);
            }
        }

        //将s1剩余的运算符依次弹出并加入s2
        while(s1.size() != 0){
            s2.add(s1.pop());
        }

        return s2;
    }

    /**
     * 判断运算符优先级
     * @param oper
     * @return
     */
    public static int getValue(String oper){
        int result = 0;
        switch (oper){
            case "+" :
                result = 1;
                break;
            case "-" :
                result = 1;
                break;
            case "*" :
                result = 2;
                break;
            case "/":
                result = 2;
                break;
            default:
                System.out.println("不存在或不支持该运算符");
                break;
        }
        return result;
    }

如果需要加入小数点,可以把运算时候的数据类型改成Double,然后加入判断小数点

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章