• 算符优先文法,中缀式求值,栈的典型应用


      栈,是比较基础,应用比较广的一种数据结构,栈和队列都可以看成是比较特殊的一些链表,其最突出的特性就是先进后出。虾米阿尼是一个比较常见的中缀表达式求值的应用,当然中缀式到后缀式的转化也是可以实现的。

      中缀式,这个我们不陌生平时大家写的式子就是中缀式的,比如2*(3-1)+(5+6)/9

      后缀式,主要考虑的是操作符的位置,操作符在操作数之后的,比如上面的中缀式可以转化为这样的后缀式:2 3 1 - * 5 6 + 9 / +,转化的规则其实也很简单,opnd1 optr opnd2 ==>opnd1 opnd2 optr,大白话来说就是直接把操作符挪到两个操作数的后面,当然了,这里的opnd不单单就是一个数有时可能是中缀式中括号里面的一坨:-)

      表达式求值这一块的内容,本人参看的是以前读数据结构时的经典教材,严蔚敏老师的。

      表达式求值的基本原理是一种常见的简明算法,“算符优先文法”,依稀记得这也是编译原理中曾经介绍过的,那么究竟什么是算符优先文法,通俗而言就是我们小学计算四则混合运算的规则:

    1)先乘除,后加减

    2)同级运算,从左到右依次计算

    3)有括号的,先算括号里面的

      具体到我们要说的,算符优先文法来看,考察一个式子,比如上面的2*(3-1)+(5+6)/9,这个式子中,我们规定“()”也是一种运算而且这种运算的优先级还比较的高。于是集合上面三条运算的规则,我们可以指定出来一个算符优先级表,计算机扫面表达式,通过查表,获得当前计算的动作。

    首先我们先定义我们的运算符表 OP = {+-*/()#},并且我们结合上面的三条运算规则,定义出下面的运算符优先级表:

     
      + - * / ( ) #
    + > > < < < > >
    - > > < < < > >
    * > > > > < > >
    / > > > > < > >
    ( < < < < < = >
    ) > > > >   >  
    # < < < < <   =

    观察上面的表格,以第一行为例,当前计算机同时碰到了+和另外一种(也有可能是+)的运算,这是选择运算级别比较高的那种运算,上面  > 说明出在行位置的运算比列位置的运算的优先级高,反则反之,=表明二者的优先级一样,空格表示这两种运算之间没有可比性,说明输入的式子有文法错误。很明显我们要把上面的这个表存储到计算机中,以便计算机在计算的时候方便计算机来查阅,我们一般使用一个二维数组来存储。还有上面的 # 使我们加入的一种运算,我们规定这种运算的优先级最低。

      下面就来简述一下,算法核心的流程。

    需要两个栈,扫描中缀表达式时,用optr栈存储中缀式里面的运算符,用opnd栈存储中缀式中的运算数。optr栈顶的运算符对应着算符优先级表第一纵裂位置的运算符,每次从中缀式总扫描到的运算符,对应着优先级表第一横行位置的运算符。于是算法的基本思想如下:

    (1)首先置操作数栈为空栈,然后置optr运算符栈的栈底为#运算。

    (2)依次扫描表达式中的每一个字符ch,若是操作数则压入opnd栈中,若是运算符则和optr栈顶的运算符比较优先级后做相应的操作(若栈顶运算符优先级高则opnd连续弹出两个数完成相应运算再把记过压入opnd中,如果optr栈顶运算符优先级低,则把当前扫描到的运算符压入optr栈中),直至整个表达式扫描完毕,这是opnd中的数值就是运算结果。

    具体的描述如下,

    /*                                                                                          
     * 中缀表达式的 求值计算                                                                              
     */                                                                                         
    public void inffixCal()                                                                     
    {                                                                                           
        int index = 0;                                                                          
                                                                                                
        while(inffixExp.charAt(index)!='#' || optr.peekFirst()!='#')                            
        {                                                                                       
            Character ch = inffixExp.charAt(index);                                             
            if(!inOP(ch))                                                                       
            {// 一般数字                                                                            
                opnd.push(Double.parseDouble(ch+""));    //于是准备取下一个字符了                           
                index ++;                                                                       
            }                                                                                   
            else{// 运算符                                                                         
                switch(Precede(optr.peekFirst(), ch))                                           
                {                                                                               
                    case -1:    // < 栈顶 符号 的优先级 低 符号入栈                                          
                        optr.push(ch);                                                          
                        index ++;                                                               
                        break;                                                                  
                                                                                                
                    case 1:        // > 即栈顶符号的优先级比较高                                               
                        Character theta = optr.pop();                                           
                        Number b = (Double) opnd.pop();                                         
                        Number a = (Double) opnd.pop();                                         
                        Double c = operate(a, b, theta);                                        
                        opnd.push(c);        // 把计算的结果再次压站                                       
                        break;                                                                  
                                                                                                
                    case 0:    // 两种运算符的优先级相等 也就是 ()                                               
                        optr.pop();    //脱括号之后 接着往后扫描                                              
                        index ++;                                                               
                        break;                                                                  
                    default:                                                                    
                        throw new RuntimeException(new Exception("您的文法有误,请检查"));                
                }                                                                               
            }                                                                                   
        }                                                                                       
        result = (Double)opnd.pop();                                                            
    }                                                                                           

    算法的实现,定义了一个expressionEvaluate类,具体的实现详见下面的代码,展看查看详情

      1 package test;
      2 
      3 import java.util.LinkedList;
      4 import java.util.Scanner;
      5 
      6 import javax.naming.InitialContext;
      7 
      8 /*
      9  * 基于"算符优先"文法 的表达式求值, 其实这也是数据程序编译器设计中常用的一种方法
     10  * 其实可以一次性 扫描中缀表达式的同时,利用算符文法来求出中缀表达式的值,由于中缀式和后缀式的相互转化使用的也是算法文法,这里就把问题分开两部分来做
     11  * 提前说一下,个人总是感觉,算法设计类的问题总是比较适合面向过程来解决,这里硬是面向对象感觉有点伪面向对象的感觉
     12  * ExpressionEvaluate    算法的主体驱动部分
     13  */
     14 
     15 public class ExpressionEvaluate {
     16     
     17     public static final String OP = "+-*/()#";    //求值系统的所有算附表
     18     public static final int[][] opPreTable={    //算符优先级表
     19         {1, 1, -1, -1, -1, 1, 1},
     20         {1, 1, -1, -1, -1, 1, 1},
     21         {1, 1, 1, 1, -1, 1, 1},
     22         {1, 1, 1, 1, -1, 1, 1},
     23         {-1, -1, -1, -1, -1, 0, Integer.MAX_VALUE},
     24         {1, 1, 1, 1, Integer.MAX_VALUE, 1, 1},
     25         {-1, -1, -1, -1, -1, -Integer.MAX_VALUE, 0}
     26     };
     27     
     28     // 定义两个工作栈
     29     private LinkedList<Character> optr;    // 存储操作符
     30     private LinkedList<? super Number> opnd;    //存储数字
     31     private StringBuffer inffixExp;        //    存储中缀表达式
     32     private StringBuffer suffixExp;        //存储后缀表达式
     33     private double result;                //表达式最终的结果
     34     
     35     {// 构造代码块 优先于 构造函数的执行
     36         init();    //初始化操作
     37     }
     38     public ExpressionEvaluate() {
     39         
     40     }
     41     public ExpressionEvaluate(String exp)
     42     {
     43         setInffixExp(exp);
     44     }
     45     
     46     public void setInffixExp (String exp)
     47     {
     48         inffixExp = new StringBuffer(exp);
     49         if(inffixExp.charAt(inffixExp.length()-1)!='#')
     50             inffixExp.append('#');
     51     }
     52     
     53     private void init()
     54     {
     55         inffixExp = new StringBuffer();
     56         suffixExp = new StringBuffer();
     57         optr = new LinkedList<Character>();
     58         opnd = new LinkedList<Number>();
     59         optr.push('#');
     60         result = 0;
     61     }
     62     
     63     public String getInffix()
     64     {
     65         return new String(inffixExp.substring(0, inffixExp.length()-1));
     66     }
     67     public String getSuffix()
     68     {
     69         return new String(suffixExp);
     70     }
     71     public Double getResult()
     72     {
     73         return result;
     74     }
     75     
     76     /*
     77      * 中缀表达式的 求值计算
     78      */
     79     public void inffixCal()
     80     {
     81         int index = 0;
     82         
     83         while(inffixExp.charAt(index)!='#' || optr.peekFirst()!='#')
     84         {
     85             Character ch = inffixExp.charAt(index);
     86             if(!inOP(ch))
     87             {// 一般数字
     88                 opnd.push(Double.parseDouble(ch+""));    //于是准备取下一个字符了
     89                 index ++;
     90             }
     91             else{// 运算符
     92                 switch(Precede(optr.peekFirst(), ch))
     93                 {
     94                     case -1:    // < 栈顶 符号 的优先级 低 符号入栈
     95                         optr.push(ch);
     96                         index ++;
     97                         break;
     98                         
     99                     case 1:        // > 即栈顶符号的优先级比较高
    100                         Character theta = optr.pop();
    101                         Number b = (Double) opnd.pop();
    102                         Number a = (Double) opnd.pop();
    103                         Double c = operate(a, b, theta);
    104                         opnd.push(c);        // 把计算的结果再次压站
    105                         break;
    106                         
    107                     case 0:    // 两种运算符的优先级相等 也就是 ()
    108                         optr.pop();    //脱括号之后 接着往后扫描
    109                         index ++;
    110                         break;
    111                     default:
    112                         throw new RuntimeException(new Exception("您的文法有误,请检查"));
    113                 }
    114             }
    115         }
    116         result = (Double)opnd.pop();
    117     }
    118     
    119     public double operate(Number a, Number b, Character theta) {
    120         double c = 0;
    121         switch(theta)
    122         {
    123             case '+':
    124                 c = (Double)a + (Double)b;
    125                 break;
    126             case '-':
    127                 c = (Double)a - (Double)b;
    128                 break;
    129             case '*':
    130                 c = (Double)a * (Double)b;
    131                 break;
    132             case '/':
    133                 if((Double)b == 0)
    134                     throw new RuntimeException(new Exception("除数不能为0"));
    135                 c = (Double)a / (Double)b;
    136                 break;
    137             default:
    138                 throw new RuntimeException(new Exception("尚不支持这种运算"));
    139         }
    140         return c;
    141     }
    142     /*
    143      * 比较栈optr栈顶符号 和 当前符号之间的优先级
    144      */
    145     public int Precede(Character peekFirst, Character ch) {
    146         return opPreTable[OP.indexOf(peekFirst)][OP.indexOf(ch)];
    147     }
    148     // 判断某个字符是不是在 运算符表中
    149     public boolean inOP(Character ch)
    150     {
    151         return OP.contains(new String (ch+""));
    152     }
    153     
    154     /*
    155      * 中缀表达式到后缀表达式
    156      * 和 中缀式 求值 非常相似
    157      */
    158     public void inffix2suffix()
    159     {
    160         suffixExp.setLength(0);
    161         optr.clear();
    162         opnd.clear();
    163         int index = 0;
    164         
    165     }
    166     
    167     public static void main(String[] args) {
    168 /*        String exp;
    169         Scanner scanner = new Scanner(System.in);    // 2*(5-1)/2+3-1
    170         
    171         System.out.println("输入一个以#结尾的表达式");
    172         exp = scanner.next();*/
    173         
    174         ExpressionEvaluate ee = new ExpressionEvaluate();
    175         ee.setInffixExp("2*3*4-1#");
    176         
    177         System.out.println(ee.getInffix());
    178         
    179         ee.inffixCal();
    180         
    181         System.out.println(ee.getResult());
    182         
    183         
    184     }
    185     
    186 }
    View Code

    上面就只实现了中缀表达式的求值,可以进一步完善,inffix2suffix中缀式到后缀式的转化,这里没有实现了,如果实现之后,具体的表达式的计算可以先把中缀式转化为后缀式,计算结果时简单的扫描后缀式就可以计算了,因为后缀式是一种无需担心运算符优先级的算式表达形式。

    下面是inffix---》suffix转化的算法描述

    (1)标识符号#压入optr栈中,开始扫描终追表达式

    (2)当ch!='#'时

      if ch>=0 && ch <=9 opnd.push(ch)

      if ch is a operator 

        if ch > optr.top()  optr.push(ch)

        if ch < optr.top()  opnd连续出两个操作数,并和当前的栈顶运算符附加到suffix中

        if ch==optr.top()  查看上面的运算符优先级表可以知道,(==)这时候直接把optr的(弹出即可,然后接着向后扫描表达式

  • 相关阅读:
    Day Six(Beta)
    Day Five (beta)
    Day Four(Beta)
    Day Three(Beta)
    Day Two(Beta)
    Day One(Beta)
    项目冲刺——总结
    beta版本贡献率
    软件工程实践总结
    团队作业--Beta版本冲刺
  • 原文地址:https://www.cnblogs.com/OliverZhang/p/6613560.html
Copyright © 2020-2023  润新知