暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

图解中缀表达式转后缀表达式及后缀表达式求值

大数据记事本 2020-07-21
3177
    在上一篇中详解了用栈实现中缀表达式求值的过程,但是如果表达式中包含括号,中缀表达式就没那么方便了,这时会将中缀表达式转成后缀表达式(又称为逆波兰表达式)来进行求值,在转换的过程中会消除括号。本篇来详解中缀表达式如何转为后缀表达式,以及后缀表达式的求值。
    根据运算符相对操作数的位置,表达式分为前缀表达式、中缀表达式和后缀表达式(前缀和后缀表达式没有括号)。

一、前缀表达式
    定义:运算符位于其相关的操作数之前
      运算:从右向左扫描表达式,遇到数字直接压入栈,遇到运算符将栈顶数值和次顶数值弹出进行运算,再将结果入栈(遇到减法和除法,用栈顶元素作为被减数或被除数)。如(3+4)*5-6 的前缀表达式为:
    - * + 3 4 5 6
    复制

        转换方法:运算符根据运算级别从右到左排,操作数根据运算级别从左到右排列,比如上面的表达式:运算级别顺序为+、*、-,从右到左排:- * +;操作数顺序为3、4、5、6,从左到右排:3 4 5 6 。组合就是:- * + 3 4 5 6。

    图解前缀表达式求值:


    二、中缀表达式 

        定义:运算符位于与其相关的操作数之间,我们常见的即为中缀表达式

        运算:需要两个栈,一个为数栈存放数字,另一个为符号栈存放操作符。

      1.通过一个index值(索引),来遍历表达式;
      2.如果发现是一个数字,就直接入数栈;
      3.如果发现扫描到的是一个符号,分如下情况:
      3.1如果发现当前的符号栈为空,直接入栈;
      3.2如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或等于栈中的操作符,就需要从数栈中pop出两个数(这里注意如果是减法和除法,次顶元素作为被减数或被除数,与前缀表达式相反),再从符号栈中pop出一个符号,进行运算,将得到的结果入数栈,然后操作符入符号栈;如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈。
      4.当表达式扫描完毕,就顺序从数栈中和符号栈中pop出相应的数和符号,并运算。
      5.最后在数栈只有一个数字,就是表达式的结
      复制

          图解参考:图解用栈实现中缀表达式求值


      三、后缀表达式

          定义:运算符位于与其相关的操作数之后

          运算:

        1.扫描表达式,如果是数直接入栈;
        2.如果是运算符,从栈中弹出两个数进行运算,然后将结果入栈;
        3.最后栈中剩余的一个值即为表达式的值
        复制

        如:(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.1s1中新的栈顶运算符进行比较;5.遇到括号时:5.1.如果是左括号“(”,则直接压入s1;5.2.如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃;6.重复步骤2-5,直到表达式的最右边;7.将s1中剩余的运算符依次弹出并压入s2;8.依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
            复制


            图解步骤如下:

            ①扫描到前7个字符时,依次入栈,如下图:(如果栈顶为左括号,下一个运算符直接入栈)

            ②当扫描到右括号“)”时,依次弹出s1栈顶的运算符压入s2,直到遇到左括号,一对括号消除;

            ③然后扫描到‘*’直接入s1,扫描到‘4’直接入s2

            ④接着扫描到第二个右括号‘)’,将s1栈顶的‘*’弹出压入s2,然后消除一对括号

            ⑤接着扫描到‘-’,优先级和s1栈顶的‘+’相同,把s1栈顶运算符弹出压入s2,然后将‘-’压入s1,最后扫描到的‘5’入s2栈

            ⑥最后将s1中的运算符依次弹出并压入s2

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

              s2弹出顺序:- 5 + * 4 + 3 2 1 
              逆序,即后缀表达式:1 2 3 + 4 * + 5 -
              复制

              代码实现

              思路: 

              1. 先将中缀表达式转换成对应的ArrayList集合,便于遍历,如上述表达式:"1+((2+3)*4)-5" 转换后应该为:[1, +, (, (, 2, +, 3, ), *, 4, ), -, 5]

              2. 将步骤①得到的List转成后缀表达式对应的List,如[1, +, (, (, 2, +, 3, ), *, 4, ), -, 5] 转换为[1, 2, 3, +, 4, *, +, 5, -] 

              注意:这里由于s2栈在转换过程中并没有pop操作,所以用ArrayList代替,这样直接遍历即可,不必逆序输出,简化了操作。
              ①先将中缀表达式转换成对应的ArrayList集合
                public static List<String> infixExpression2List(String infixExpression) {
                ArrayList<String> ls = new ArrayList<String>();
                int i = 0;
                String str;//对多位数进行拼接
                char ch;//每遍历到一个字符,就放入ch
                do {
                //如果ch是一个非数字,加入ls
                if ((ch = infixExpression.charAt(i)) < 48 || (ch = infixExpression.charAt(i)) > 57) {
                ls.add("" + ch);
                i++;
                } else {
                str = "";//先将str置空
                //这里判断i的范围是因为内层循环结束后才会判断外层循环条件,如果不加会在内层循环时出现下标越界
                while (i < infixExpression.length() && (ch = infixExpression.charAt(i)) >= 48 && (ch = infixExpression.charAt(i)) <= 57) {
                str += ch;//拼接多位数
                i++;
                }
                ls.add(str);
                }
                } while (i < infixExpression.length());
                return ls;
                }
                复制

                比较运算符优先级的方法

                  class Operation {
                  private static int ADD = 1;
                  private static int SUB = 1;
                  private static int MUL = 2;
                  private static int DIV = 2;


                  public static int getValue(String operation) {
                  int result = 0;
                  switch (operation) {
                  case "+":
                  result = ADD;
                  break;
                  case "-":
                  result = SUB;
                  break;
                  case "*":
                  result = MUL;
                  break;
                  case "/":
                  result = DIV;
                  break;
                  default:
                  System.out.println("不存在的运算符");
                  break;
                  }
                  return result;
                  }
                  }
                  复制

                  ③将步骤①得到的List转成后缀表达式对应的List

                    public static List<String> infix2SuffixExpressionList(List<String> infixList) {
                    Stack<String> s1 = new Stack<String>();//操作符栈
                    //由于在整个转换过程中s2栈都没有pop操作,而且后面需要逆序输出,为了简化,可以不用栈结构,改用List<String> s2代替
                    List<String> s2 = new ArrayList<String>();




                    //遍历infixList
                    for (String item : infixList) {
                    //如果是一个数,则入s2
                    if (item.matches("\\d+")) {
                    s2.add(item);
                    } else if (item.equals("(")) {//如果是左括号,直接入s1栈
                    s1.push(item);
                    } else if (item.equals(")")) {//如果是右括号,则依次弹出s1栈顶的运算符,并加入s2,直到遇到左括号为止,此时将这一对括号丢弃;
                    while (!s1.peek().equals("(")) {
                    s2.add(s1.pop());
                    }
                    s1.pop();//去除s1中对应的左括号,很重要!!!
                    } else {
                    //如果是运算符,比较其与s1栈顶运算符的优先级:
                    //1.如果s1为空,或运算符栈顶为左括号“(”,则直接将此运算符入栈;
                    //2.否则,若优先级比栈顶运算符的高,也将运算符压入s1(注意必须是高,等于或低于都不行)
                    //3.否则,将s1栈顶的运算符弹出并压入s2中,再次转到4.1与s1中新的栈顶运算符进行比较;
                    //当item的优先级小于等于s1栈顶的优先级时,将s1栈顶的运算符弹出并加入s2中,再次转到1与s1中新的栈顶运算符进行比较
                    while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)) {
                    s2.add(s1.pop());
                    }
                    //将item入栈s1
                    s1.push(item);
                    }
                    }
                    //将s1中的剩余运算符弹出并加入s2
                    while (s1.size() != 0) {
                    s2.add(s1.pop());
                    }
                    return s2;//由于加入的是一个List,本身有序,所以正常输出即为后缀表达式而不必逆序
                    }
                    复制

                    ④通过步骤③得到的list计算后缀表达式的值

                      public static int calculator(List<String> ls){
                      Stack<String> stack = new Stack<String>();
                      //遍历ls
                      for (String item : ls) {
                      if (item.matches("\\d+")){//如果是数直接入栈
                      stack.push(item);
                      }else{
                      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("运算符有误");
                      }
                      stack.push(res+"");
                      }
                      }
                      //栈最后剩下的数即为运算结果
                      return Integer.parseInt(stack.pop());
                      }
                      复制

                      图解后缀表达式求值



                      文章转载自大数据记事本,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                      评论