后綴表達式求值
后綴表達式又叫逆波蘭表達式,其求值過程可以用到棧來輔助存儲。例如要求值的后綴表達式為:1 2 3 + 4 * + 5 -,則求值過程如下:
- 遍歷表達式,遇到數字時直接入棧,棧結構如下
2. 接着讀到 “+”操作符,則將棧頂和次棧頂元素出棧與操作符進行運算,執行 2 + 3操作,並將結果5壓入棧中,此時棧結構如下
3. 繼續讀到4,是數字則直接壓棧,此時棧結構如下
4. 繼續向后讀取,此時讀取到操作符“*”,則將棧頂和次棧頂元素出棧與操作符進行運算,即執行 5 * 4 ,然后將結果20壓入棧中,此時棧結構如下
5. 繼續向后讀取,此時讀到操作符“+”,則將棧頂和次棧頂元素出棧與操作符進行運算,即執行1 + 20,然后將結果21壓入棧中,此時棧結構如下
6. 繼續向后讀取,此時讀到數字5,則直接將數字壓棧,棧結構如下
7. 讀取到最后一個為操作符,將棧頂和次棧頂元素出棧與操作符進行運算,即執行 21- 5(注意順序 次棧頂-棧頂),然后將結果16壓入棧中,此時棧結構如下
此時棧頂元素即為表達式的結果。(注:為方便理解,這里棧頂指針向下移動后,上面元素直接去掉了,實際情況數據還會存在對應位置,只是通過棧頂指針讀取不到,等待GC)
中綴表達式轉后綴表達式
中綴表達式為我們人類能識別的方式,而后綴表達式是計算機進行運算的方式(即我們上述的過程)。
轉換規則
1)我們使用一個stack棧結構存儲操作符,用一個List結構存儲后綴表達式結果
2)首先讀取到數字,直接存入list中
3)當讀取到左括號"("時,直接壓棧,當讀取到運算符時,分兩種情況討論
a.當運算符棧為空或者棧頂操作符的優先級小於當前運算符優先級時(如+和-的優先級低於 * 和 /),直接入棧
b.當運算符不為空時且棧頂操作符的優先級大於或等於當前運算符優先級時,循環執行出棧操作並加入list中,直到遇到優先級小於當前運算符的元素為止。循環執行完后再將當前運算符壓棧。另外需要注意的是,只有遇到右括號時,左括號才出棧
4) 當遇到右括號")"時,循環執行出棧操作並加入到list中,直到遇到左括號為止。並將左括號彈出,但不加入list中
5) 表達式的值讀取完后,將操作符棧中的所有元素彈出並加入到list中
執行完上面步驟后,list中存儲的順序即為我們轉換后的后綴表達式的結果
轉換實例
下面利用上面定義的轉換規則,將表達式 1+((2+3)*4)-5 以圖解的方式描述其轉換過程
1.首先定義一個存儲操作符的棧 Stack<String> stack = new Stack<>() ,和一個存儲最終后綴表達式的列表 List<String> list = new ArrayList<>()
2.讀取表達式,首先讀取到數字 1 ,按照上述規則,直接添加至list中。此時stack和list結構如下
3.然后讀取到操作符 + ,此時stack為空,按照上述規則,直接入棧,此時stack和list結構如下
4.接下來的兩次讀取都是左括號,按照我們的規則,左括號直接入棧,此時stack和list結構如下
5.接着讀取到數字2,按照我們的規則,數字直接加入list中,此時stack和list結構如下
6.接着讀取到操作符+,按照我們的規則,此時棧不為空且棧頂元素為左括號,而只有遇到右括號時,左括號才出棧,所以+運算符直接入棧,此時stack和list結構如下
7. 接着讀取到數字3,根據我們的規則,數字直接加入list中,此時stack和list結構如下
8. 繼續向后讀取,讀到到右括號 ")",按照我們的規則,執行stack出棧並加入list中操作,直到遇到左括號,並將左括號彈出,但不加入list中,此時stack和list結構如下
9.接着讀取到操作符 * ,按照我們的規則,此時棧頂元素為左括號,只需將操作符壓棧即可,此時stack和list結構如下
10.接下來讀取到數字4,按照規則直接將數字加入list中即可,此時stack和list結構如下
11.接下來讀取到右括號")",按照我們的規則,執行stack出棧並加入list中操作,直到遇到左括號,並將左括號彈出,但不加入list中,此時stack和list結構如下
12.繼續向后讀取,此時讀取到操作符-,按照我們的規則,當棧不為空且當前優先級小於等於棧頂操作符優先級時,循環執行出棧並加入list操作。循環執行完再將當前操作符入棧
13.讀取最后一個元素為數字5,按照規則,直接加入list中即可。當表達式讀取完后,依此彈出操作符棧中的所有元素,並加入list中,此時stack和list結構如下
此時list中的順序即為我們轉換后的后綴表達式的順序,即:1 2 3 + 4 * + 5 -
轉換代碼
1 private static List<String> parseToSuffixExpression(List<String> expressionList) { 2 //創建一個棧用於保存操作符 3 Stack<String> opStack = new Stack<>(); 4 //創建一個list用於保存后綴表達式 5 List<String> suffixList = new ArrayList<>(); 6 for(String item : expressionList){ 7 //得到數或操作符 8 if(isOperator(item)){ 9 //是操作符 判斷操作符棧是否為空 10 if(opStack.isEmpty() || "(".equals(opStack.peek()) || priority(item) > priority(opStack.peek())){ 11 //為空或者棧頂元素為左括號或者當前操作符大於棧頂操作符直接壓棧 12 opStack.push(item); 13 }else { 14 //否則將棧中元素出棧如隊,直到遇到大於當前操作符或者遇到左括號時 15 while (!opStack.isEmpty() && !"(".equals(opStack.peek())){ 16 if(priority(item) <= priority(opStack.peek())){ 17 suffixList.add(opStack.pop()); 18 } 19 } 20 //當前操作符壓棧 21 opStack.push(item); 22 } 23 }else if(isNumber(item)){ 24 //是數字則直接入隊 25 suffixList.add(item); 26 }else if("(".equals(item)){ 27 //是左括號,壓棧 28 opStack.push(item); 29 }else if(")".equals(item)){ 30 //是右括號 ,將棧中元素彈出入隊,直到遇到左括號,左括號出棧,但不入隊 31 while (!opStack.isEmpty()){ 32 if("(".equals(opStack.peek())){ 33 opStack.pop(); 34 break; 35 }else { 36 suffixList.add(opStack.pop()); 37 } 38 } 39 }else { 40 throw new RuntimeException("有非法字符!"); 41 } 42 } 43 //循環完畢,如果操作符棧中元素不為空,將棧中元素出棧入隊 44 while (!opStack.isEmpty()){ 45 suffixList.add(opStack.pop()); 46 } 47 return suffixList; 48 } 49 /** 50 * 判斷字符串是否為操作符 51 * @param op 52 * @return 53 */ 54 public static boolean isOperator(String op){ 55 return op.equals("+") || op.equals("-") || op.equals("*") || op.equals("/"); 56 } 57 58 /** 59 * 判斷是否為數字 60 * @param num 61 * @return 62 */ 63 public static boolean isNumber(String num){ 64 return num.matches("\\d+"); 65 } 66 67 /** 68 * 獲取操作符的優先級 69 * @param op 70 * @return 71 */ 72 public static int priority(String op){ 73 if(op.equals("*") || op.equals("/")){ 74 return 1; 75 }else if(op.equals("+") || op.equals("-")){ 76 return 0; 77 } 78 return -1; 79 }
這里為了方便操作,將原中綴表達式字符串轉換為list結構,轉換list的代碼如下
1 /** 2 * 將表達式轉為list 3 * @param expression 4 * @return 5 */ 6 private static List<String> expressionToList(String expression) { 7 int index = 0; 8 List<String> list = new ArrayList<>(); 9 do{ 10 char ch = expression.charAt(index); 11 if(ch < 47 || ch > 58){ 12 //是操作符,直接添加至list中 13 index ++ ; 14 list.add(ch+""); 15 }else if(ch >= 47 && ch <= 58){ 16 //是數字,判斷多位數的情況 17 String str = ""; 18 while (index < expression.length() && expression.charAt(index) >=47 && expression.charAt(index) <= 58){ 19 str += expression.charAt(index); 20 index ++; 21 } 22 list.add(str); 23 } 24 }while (index < expression.length()); 25 return list; 26 }
注:char類型本質為int類型,查看assic碼表可知,0~9對應的char在 47~58之間,所以代碼依此來判斷是數字還是操作符。另外代碼中有判斷多位數情況,請注意
下面展示測試代碼
1 public static void main(String []args){ 2 3 String expression = "1+((2+3)*4)-5"; 4 List<String> expressionList = expressionToList(expression); 5 System.out.println("expressionList="+expressionList); 6 //將中綴表達式轉換為后綴表達式 7 List<String> suffixList = parseToSuffixExpression(expressionList); 8 System.out.println(suffixList); 9 }
測試結果如下:
與我們上述描述的結果相同,以上即為中綴表達式轉換后綴表達式的過程及相關代碼。另外附上根據后綴表達式求值的代碼,感興趣的可以參考
1 /** 2 * 根據后綴表達式list計算結果 3 * @param list 4 * @return 5 */ 6 private static int calculate(List<String> list) { 7 Stack<Integer> stack = new Stack<>(); 8 for(int i=0; i<list.size(); i++){ 9 String item = list.get(i); 10 if(item.matches("\\d+")){ 11 //是數字 12 stack.push(Integer.parseInt(item)); 13 }else { 14 //是操作符,取出棧頂兩個元素 15 int num2 = stack.pop(); 16 int num1 = stack.pop(); 17 int res = 0; 18 if(item.equals("+")){ 19 res = num1 + num2; 20 }else if(item.equals("-")){ 21 res = num1 - num2; 22 }else if(item.equals("*")){ 23 res = num1 * num2; 24 }else if(item.equals("/")){ 25 res = num1 / num2; 26 }else { 27 throw new RuntimeException("運算符錯誤!"); 28 } 29 stack.push(res); 30 } 31 } 32 return stack.pop(); 33 }
測試運算代碼如下
1 public static void main(String []args){ 2 3 String expression = "1+((2+3)*4)-5"; 4 List<String> expressionList = expressionToList(expression); 5 System.out.println("中綴表達式轉為list結構="+expressionList); 6 //將中綴表達式轉換為后綴表達式 7 List<String> suffixList = parseToSuffixExpression(expressionList); 8 System.out.println("對應的后綴表達式列表結構="+suffixList); 9 //根據后綴表達式計算結果 10 int calculateResult = calculate(suffixList); 11 System.out.printf(expression+"=%d\n",calculateResult); 12 }
計算結果如下
總結
中綴表達式轉后綴表達式的難點在於轉換規則,當然這個規則是研究算法的人已經幫我們制定好的,我們只需要按照這個規則實現代碼即可。如果上述代碼有問題可在留言區回復,謝謝