棧,是比較基礎,應用比較廣的一種數據結構,棧和隊列都可以看成是比較特殊的一些鏈表,其最突出的特性就是先進后出。蝦米阿尼是一個比較常見的中綴表達式求值的應用,當然中綴式到后綴式的轉化也是可以實現的。
中綴式,這個我們不陌生平時大家寫的式子就是中綴式的,比如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 }
上面就只實現了中綴表達式的求值,可以進一步完善,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的(彈出即可,然后接着向后掃描表達式