文章目录

  • 1.Java前缀、中缀及后缀表达式(看符号位置)
  • 1.1.前缀表达式
  • 1.2.中缀表达式
  • 1.3.后缀表达式(逆波兰表达式)


1.Java前缀、中缀及后缀表达式(看符号位置)

1.1.前缀表达式
  • 前缀表达式的运算符位于操作符之前2
  • 例如:中缀表达式 (3+4)*5-6 对应的前缀表达式为:- * + 3 4 5 6
  • 中缀表达式转前缀表达式的步骤: (1) 初始化两个栈:运算符栈S1和储存中间结果的栈S2; (2) 从右至左扫描中缀表达式;

(3) 遇到操作数时,将其压入S2;

(4) 遇到运算符时,比较其与S1栈顶运算符的优先级:

(4-1) 如果S1为空,或栈顶运算符为右括号“)”,则直接将此运算符入栈;

(4-2) 否则,若优先级比栈顶运算符的较高或相等,也将运算符压入S1;

(4-3) 否则,将S1栈顶的运算符弹出并压入到S2中,再次转到(4-1)与S1中新的栈顶运算符相比较;

(5) 遇到括号时:

(5-1) 如果是右括号“)”,则直接压入S1;

(5-2) 如果是左括号“(”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止,此时将这一对括号丢弃;

(6) 重复步骤(2)至(5),直到表达式的最左边;

(7) 将S1中剩余的运算符依次弹出并压入S2;

(8) 依次弹出S2中的元素并输出,结果即为中缀表达式对应的前缀表达式。

例如: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:

从右至左扫描,将6、5、4、3压入堆栈
遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
最后是-运算符,计算出35-6(先弹出的数—后弹出的数)的值,即29,由此得出最终结果

计算前缀表达式实现代码

package example.project.expression;

import java.util.Stack;

/**
 * @author tian
 * @Package PACKAGE_NAME
 * @date 2022/7/23 16:23
 * @description 前缀表达式实现类
 */
public class PrefixExpression {

        public static boolean recognize(char ch) {
            return ch == '*' || ch == '+' || ch == '-' || ch == '/';
        }

        public static int operateNum(int num1, int num2, char ch) {
            int res = 0;
            switch (ch) {
                case '+':
                    res = num1 + num2;
                    break;
                case '-':
                    res = num1 - num2;
                    break;
                case '*':
                    res = num1 * num2;
                    break;
                case '/':
                    res = num1 / num2;
                    break;
                default:
                    System.out.println("无法识别符号");
            }
            return res;
        }

        public static void main(String[] args) {
            //数字栈
            Stack<String> valueStack = new Stack<String>();
            //输入字符串
//        Scanner sc = new Scanner(System.in);
//        String s = sc.nextLine();
            String s = "- * + 3 4 5 6";
            String[] list = s.split(" ");
            String str = "";
            int length = list.length;
            for (int i = length -1; i >= 0; i--) {
                str = list[i];
                if(str.length() != 1){//这个一定为多位数,直接入栈
                    valueStack.push(str);
                }else {//判断是运算符还是数字
                    if(recognize(str.charAt(0))){//如果为运算符
                        int num1 = Integer.parseInt(valueStack.pop());
                        int num2 = Integer.parseInt(valueStack.pop());
                        //计算值
                        valueStack.push(String.valueOf(operateNum(num1, num2, str.charAt(0))));
                    }else {//如果为数字
                        valueStack.push(str);
                    }
                }

            }
            System.out.println("数据栈的个数=" + valueStack.size());
            System.out.printf("%s=%s", s, valueStack.pop());
        }
}
1.2.中缀表达式
  • 中缀表达式为人们常用的数学计算表达式
  • 例如 :(3+4)*5-6
  • 中缀表达式的求值往往为人们所熟悉,但是对计算机来说不方便操作,若要实现对中缀表达式的求值则需要许多逻辑判断。因此,在计算结果时,往往会将中缀表达式转成其他表达式进行计算(一般转换成后缀表达式)

实现代码

package example.project.expression;


import java.util.Arrays;
import java.util.Stack;

/**
 * @author tian
 * @Package example.project.expression
 * @date 2022/7/23 16:27
 * @description 中缀表达式
 */
public class NifixExpression {
    //Java使用的就是中缀表达式-" ( 10 + 20 / 2 * 3 ) /  2 + 8"
    private static final String nifix="(10+20/2*3)/2+8";

    public static void main(String[] args) {
        int result = evaluateExpression(nifix);
        System.out.println("计算结果为:"+result);
    }
    /**
     * 1.格式化表达式
     *
     */
    private static String formatExpression(String expression){
        //拼接字符串
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < expression.length(); i++) {
            //获取字符
            char c = expression.charAt(i);
            //如果为操作符,则两边添加空格并添加进stringBuilder
            if(c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/'){
                stringBuilder.append(' ');
                stringBuilder.append(c);
                stringBuilder.append(' ');
            }else {
                //否则添加数加两边空格
                stringBuilder.append(c);
            }
        }
        return stringBuilder.toString();

    }
    /**
     * 2.栈实现中缀表达式计算需要建立两个栈;
     * 符号栈    +     数字栈
     * :
     2.1 符号栈如果为空,或者栈顶为左括号,则可以直接进栈
     2.2 如果栈顶符号优先级比将要入栈的符号优先级低,则可以
     直接进栈
     2.3 如果栈顶符号优先级与将要入栈的优先级相等,则先弹出栈顶
     ,同时弹出数字栈的两个数字使用弹出的符号进行计算,运算结果
     再压入数字栈,最后符号入栈
     2.4 如果将要入栈的符号为右括号,则弹出栈顶并使用弹出的符号对数字栈
     弹出的两个数字进行计算,重复过程直到符号栈栈顶元素为左括号为止
     此时为处理完了括号的内容。
     2.5 最后对最后一个符号和两个数字进行特殊操作
     */
    private static int evaluateExpression(String expression){
        /*
          stack继承了vecter,自己独有的方法只有下面5个:
          1.Object push(Object element):将元素推送到堆栈顶部。
          2.Object pop():移除并返回堆栈的顶部元素。如果我们在调用堆栈为空时调用pop(),则抛出’EmptyStackException’异常。
          3.Object peek():返回堆栈顶部的元素,但不删除它。
          4.boolean empty():如果堆栈顶部没有任何内容,则返回true。否则,返回false。
          5.int search(Object element):确定对象是否存在于堆栈中。如果找到该元素,它将从堆栈顶部返回元素的位置。否则,它返回-1。
         */
        //符号栈
        Stack<Character> operatorStack = new Stack<Character>();
        //数字栈
        Stack<Integer> numberStack = new Stack<Integer>();
        //1.格式化表达式
        String formatExpression = formatExpression(expression);
        System.out.println(formatExpression);
        //切割格式化字符串
        String[] strings = formatExpression.split(" ");
        System.out.println(Arrays.toString(strings));
        //遍历表达式
        for (String string : strings) {
            //1.如果遇到空字符,则直接跳过
            if(string.length() == 0){
                continue;
            }
            //如果字符串长度大于1.必然是数字,比如10、20
            //如果字符串长度等于1,一种是符号、一种是数字
            //跳过取出的字符串的第一个字符是符号,证明改字符串就是符号
            //string.chatAt(0)为符号
            //2.string 读入字符串的第一个字符是+或-的情况
            else if(string.charAt(0) == '+' || string.charAt(0) == '-'){
                //operatorStack.peek()符号栈的栈顶
                while(!operatorStack.isEmpty() && (operatorStack.peek() == '+'
                        || operatorStack.peek() == '-'
                        || operatorStack.peek() == '*'
                        || operatorStack.peek() == '/' )){
                    //计算处理
                    processAnOperator(operatorStack,numberStack);
                }
                //栈内元素处理完时,将待进栈元素进栈
                operatorStack.push(string.charAt(0));
            }
            //3.读入字符为 * 或 /时
            else if(string.charAt(0) == '*' || string.charAt(0) == '/'){
                while(!operatorStack.isEmpty() && (operatorStack.peek() == '*' || operatorStack.peek() == '/' )){
                    //计算处理
                    processAnOperator(operatorStack,numberStack);
                }
                operatorStack.push(string.charAt(0));
            }
            //4.读入字符为左括号时直接进栈
            else if(string.charAt(0) == '('){
                operatorStack.push(string.charAt(0));
            }
            //5.读入字符为右括号时,此时要对左括号和右括号之间的数和符号进行处理
            else if(string.charAt(0) == ')'){
                //只要符号栈栈顶不是左括号,进行循环处理
                while(operatorStack.peek() != '('){
                    processAnOperator(operatorStack,numberStack);
                }
                //处理完括号内容时,直接弹出左括号
                operatorStack.pop();
            }
            //6.当读入字符为数字时
            else {
                numberStack.push(Integer.parseInt(string));
            }
        }
        //最后一步,对最后一组运算进行处理
        while (!operatorStack.isEmpty()){
            processAnOperator(operatorStack,numberStack);
        }
        //将压入栈顶元素弹出就是最后的结果
        return numberStack.pop();
    }

    /**
     * 计算处理-处理函数
     * @param operatorStack 符号栈
     * @param numberStack 数字栈
     */
    private static void processAnOperator(Stack<Character> operatorStack, Stack<Integer> numberStack) {
         //运算数1
        int num1 = numberStack.pop();
        //运算数2
        int num2 = numberStack.pop();
        //运算数3
        char c = operatorStack.pop();
        switch (c){
            case '+':{
                numberStack.push(num2 + num1);
                break;
            }
            case '-':{
                numberStack.push(num2 - num1);
                break;
            }
            case '*':{
                numberStack.push(num2 * num1);
                break;
            }
            case '/':{
                numberStack.push(num2 / num1);
                break;
            }
        }
    }
}
1.3.后缀表达式(逆波兰表达式)
  • 与前缀表达式相似,只是运算符位于操作数之后-运算符要算优先级
  • 例如: 中缀表达式 (3+4)*5-6 对应的后缀表达式为 3 4 + 5 * 6 -

中缀表达式

后缀表达式

a+b

a b +

a+(b-c)

a b c - +

a+(b-c)*d

a b c - d * +

a+d*(b-c)

a d b c - * +

a=1+3

a 1 3 + =

  • 后缀表达式的计算机求值:

从左至右扫描表达式,遇到数字时,将数字压入数栈,遇到运算符时,弹出数栈的栈顶的两个数,并用运算符对它们做相应的运算,将结果入栈。重复上述过程直到表达式扫描完毕,最后栈中的数值即为后缀表达式的运算结果。

实现代码

package example.project.expression;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * @author tian
 * @Package example.project.expression
 * @date 2022/7/23 16:26
 * @description 后缀表达式
 */
public class SuffixExpression {

        public static void main(String[] args) {

            //先定义给逆波兰表达式
            //(30+4)×5-6  => 30 4 + 5 × 6 - => 164
            // 4 * 5 - 8 + 60 + 8 / 2 => 4 5 * 8 - 60 + 8 2 / +
            //测试
            //说明为了方便,逆波兰表达式 的数字和符号使用空格隔开
            //String suffixExpression = "30 4 + 5 * 6 -";
            String suffixExpression = "4 5 * 8 - 60 + 8 2 / +"; // 76
            //思路
            //1. 先将 "3 4 + 5 × 6 - " => 放到ArrayList中
            //2. 将 ArrayList 传递给一个方法,遍历 ArrayList 配合栈 完成计算

            List<String> list = getListString(suffixExpression);
            System.out.println("rpnList=" + list);
            int res = calculate(list);
            System.out.println("计算的结果是=" + res);

        }
        //将一个逆波兰表达式, 依次将数据和运算符 放入到 ArrayList中
        public static List<String> getListString(String suffixExpression) {
            //将 suffixExpression 分割
            String[] split = suffixExpression.split(" ");
            List<String> list = new ArrayList<String>();
            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,由此得出最终结果
    */

        public static int calculate(List<String> ls) {
            // 创建栈, 只需要一个栈即可
            Stack<String> stack = new Stack<String>();
            // 遍历 ls
            for (String item : ls) {
                // 这里使用正则表达式来取出数
                if (item.matches("\\d+")) { // 匹配的是多位数
                    // 入栈
                    stack.push(item);
                } else {
                    // pop出两个数,并运算, 再入栈
                    int num2 = Integer.parseInt(stack.pop());
                    int num1 = Integer.parseInt(stack.pop());
                    int res = 0;
                    if (item.equals("+")) {
                        res = num1 + num2;
                    } else if (item.equals("-")) {
                        res = num1 - num2;
                    } else if (item.equals("*")) {
                        res = num1 * num2;
                    } else if (item.equals("/")) {
                        res = num1 / num2;
                    } else {
                        throw new RuntimeException("运算符有误");
                    }
                    //把res 入栈
                    stack.push("" + res);
                }

            }
            //最后留在stack中的数据是运算结果
            return Integer.parseInt(stack.pop());
        }

}