算符優先文法,中綴式求值,棧的典型應用


  棧,是比較基礎,應用比較廣的一種數據結構,棧和隊列都可以看成是比較特殊的一些鏈表,其最突出的特性就是先進后出。蝦米阿尼是一個比較常見的中綴表達式求值的應用,當然中綴式到后綴式的轉化也是可以實現的。

  中綴式,這個我們不陌生平時大家寫的式子就是中綴式的,比如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的(彈出即可,然后接着向后掃描表達式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM