前言:不斷學習就是程序員的宿命。---距離2021考研還有101天
目前正在看數據結構---棧,棧有很多應用比如我們IDE的{}、[]這些成對出現的括號匹配問題,假如我們少寫一個或多寫一個IDE就會幫我們檢測出來;又比如中綴表達式的機算(是機算);以及我們熟悉的遞歸算法中都有棧的身影。下面記錄一下用棧來實現中綴表達式的計算
Github代碼地址:https://github.com/Simple-Coder/data-structure
一、中綴表達式
舉個栗子,((15÷(7-(1+1)))×3)-(2+(1+1)),就是我們熟悉的算術表達式,由3個部分組成:操作數、運算符、界限符。
中綴表達式:a+b,運算符+在操作數ab的中間
后綴表達式(逆波蘭式):ab+,運算符在操作數ab的后邊
前綴表達式(波蘭表達式):+ab,運算符在操作數ab的前邊
上述式子我們肉眼手算就知道先進行括號中的運算:先算(1+1)再算(1+1)×3……但是機器可是不知道的!有沒有一種表達式可以消除這些括號?於是波蘭數學家就貢獻給我們:后綴表達式也就是逆波蘭表達式,將運算符放在兩個操作數后邊就消除了這些括號。
二、中綴表達式轉后綴表達式(手算)
栗子:A+B*(C/D)-E/F
思想:
①確定中綴表達式中各個運算符的運算順序(肉眼觀察法!)
②選擇下一個運算符,按照【左操作數 右操作數 運算符】的方式組合成一個新的操作數
③如果還有運算符沒被處理,就繼續②
思想有了,呢就操練一把!
手算結果可能有很多種例如:ABCD/*+EF/-
三、后綴表達式的計算(手算)
栗子:((15÷(7-(1+1)))×3)-(2+(1+1))轉后綴表達式:15 7 1 1 + - ÷ 3 × 2 1 1 + + -
后綴表達式的手算方法:
從左向右掃描,每遇到一個運算符,就讓運算符前邊最近的兩個操作數執行對應運算,合體為一個操作數。(需要主要兩個操作數的左右順序。例如:A÷B與B÷A是不一樣的)
四、后綴表達式的計算(機算)
栗子:A+B-C*D/E+F(前綴)--->后綴表達式:AB+CD*E/-F+
用棧來實現后綴表達式的機算:
①從左向右掃描下一個元素,直到處理完所有元素
②若掃描到操作數則壓入棧,並回到①;否則執行③
③若掃描到運算符,則依次彈出棧頂2個元素,執行相應運算,運算結果壓回棧頂,回到①
思想有了,呢就操練一把!實現如下:
依次掃描后綴表達式:AB+CD*E/-F+
①掃描A(操作數)直接壓入棧
②掃描B(操作數)直接壓入棧
③掃描+(運算符)依次彈出棧頂兩個元素B、A(先彈出的B是“右操作數”)執行對應運算:A+B,並將執行結果A+B壓入棧中
④掃描C(操作數)直接壓入棧
⑤掃描D(操作數)直接壓入棧
⑥掃描*(運算符)依次彈出棧頂兩個元素D、C(先彈出的D是“右操作數”)執行對應運算:C*D,並將執行結果C*D壓入棧中
⑦掃描E(操作數)直接壓入棧
⑧掃描/(運算符)依次彈出棧頂兩個元素E、C*D(先彈出的E是“右操作數”)執行對應運算:C*D/E,並將執行結果C*D/E壓入棧中
⑨掃描-(運算符)依次彈出棧頂兩個元素C*D/E、A+B(先彈出的C*D/E是“右操作數”)執行對應運算:A+B-C*D/E,並將執行結果A+B-C*D/E壓入棧中
⑩掃描F(操作數)直接壓入棧
⑪掃描+(運算符)依次彈出棧頂兩個元素F、A+B-C*D/E(先彈出的F是“右操作數”)執行對應運算:A+B-C*D/E+F,並將執行結果A+B-C*D/E+F壓入棧中
至此所有字符掃描完畢,棧中唯一一個元素為運行結果。
五、中綴轉后綴表達式的計算(機算)
思想:初始化一個棧,用於保存暫時還不能確定運算順序的運算符:
從左向右處理各個元素,直到處理完所有元素,期間可能遇到以下3種情況:
①遇到操作數:直接加入后綴表達式
②遇到界限符:遇到“(”直接入棧;遇到“)”則依次彈出棧內運算符並加入后綴表達式,直到彈出“(”為止。這里需要注意“()”均不加入后綴表達式
③遇到運算符:依次彈出棧中優先級高於或等於當前運算符的所有運算符並加入后綴表達式,若遇到“(”或棧空則停止彈出;最后再將當前運算符壓入棧
最后掃描完所有字符后,將棧中剩余元素依次彈出,並加入后綴表達式。
思想有了,呢就操練一把!
1、無界限符轉換
例子:A+B-C*D/E+F(中綴表達式)從左向右開始掃描各個元素:
①掃描A(操作數)直接加入后綴表達式:A
②掃描+(運算符):需要依次彈出棧中優先級高於或等於當前運算符(+)的所有運算符加入后綴表達式,若遇到“(”或棧空則停止彈出-------由於棧空則直接退出,最后當前運算符(+)入棧
③掃描B(操作數)直接加入后綴表達式:AB
④掃描-(運算符):需要依次彈出棧中優先級高於或等於當前運算符(-)的所有運算符加入后綴表達式,若遇到“(”或棧空則停止彈出--------由於目前棧中有+與當前運算符(-)優先級相等,需要彈出棧中+加入后綴表達式:AB+,最后將當前運算符-壓入棧中
⑤掃描C(操作數):直接加入后綴表達式AB+C
⑥掃描*(運算符):需要依次彈出棧中優先級高於或等於當前運算符(*)的所有運算符加入后綴表達式,若遇到“(”或棧空則停止彈出--------由於當前棧中運算符(-)優先級小於當前運算符(*),不再彈出;最后當前運算符(*)入棧
⑦掃描D(操作數):直接加入后綴表達式AB+CD
⑧掃描/(運算符):需要依次彈出棧中優先級高於或等於當前運算符(/)的所有運算符加入后綴表達式,若遇到“(”或棧空則停止彈出-----當前棧頂元素*優先級等於當前運算符(*),彈出*加入后綴表達式:AB+CD*;然后棧中-優先級小於/不再彈出;最后當前運算符(/)入棧
⑨掃描E(操作數):直接加入后綴表達式AB+CD*E
⑩掃描+(運算符):需要依次彈出棧中優先級高於或等於當前運算符(/)的所有運算符加入后綴表達式,若遇到“(”或棧空則停止彈出------當前棧頂元素(-)等於當前運算符(+),彈出(-)加入后綴表達式:AB+CD*E/-;最后當前運算符(+)入棧
⑪掃描F(操作數):直接加入后綴表達式AB+CD*E/-F
⑫掃描完所有字符后,將棧中剩余元素依次彈出,並加入后綴表達式:AB+CD*E/-F+
2、有界限符轉換
例子:A+B*(C-D)-E/F----帶界限符的中綴表達式轉后綴表達式的計算…………還是上述思想,繼續操練
從左向右依次掃描
①掃描A(操作數)直接加入后綴表達式:A
②掃描+(運算符):需要依次彈出棧中優先級高於或等於當前運算符(+)的所有運算符加入后綴表達式,若遇到“(”或棧空則停止彈出-------由於棧空則直接退出,最后當前運算符(+)入棧
③掃描B(操作數)直接加入后綴表達式:AB
④掃描*(運算符):需要依次彈出棧中優先級高於或等於當前運算符(*)的所有運算符加入后綴表達式,若遇到“(”或棧空則停止彈出-------由於棧頂元素“+”優先級小於當前運算符“*”,不彈出任何元素,最后當前運算符(*)入棧
⑤掃描((界限符):遇到“(”直接入棧;遇到“)”則依次彈出棧內運算符並加入后綴表達式,直到彈出“(”為止。-----當前字符“(”直接入棧
⑥掃描C(操作數)直接加入后綴表達式:ABC
⑦掃描-(運算符):需要依次彈出棧中優先級高於或等於當前運算符(-)的所有運算符加入后綴表達式,若遇到“(”或棧空則停止彈出-----由於棧頂元素是"("停止彈出,當前運算符"+"入棧
⑧掃描D(操作數):直接加入后綴表達式:ABCD
⑨掃描)(界限符):遇到“(”直接入棧;遇到“)”則依次彈出棧內運算符並加入后綴表達式,直到彈出“(”為止。-----依次彈出“-”加入后綴表達式:ABCD-
⑩掃描-(運算符):需要依次彈出棧中優先級高於或等於當前運算符(-)的所有運算符加入后綴表達式,若遇到“(”或棧空則停止彈出------由於棧頂元素“*”優先級高於當前運算符“-”,彈出“*”加入后綴表達式;此時棧頂元素“+”優先級等於當前運算符“-”,彈出“+”加入后綴表達式:ABCD-*+,最后當前運算符“-”入棧
⑪掃描E(操作數):直接加入后綴表達式:ABCD-*+E
⑫掃描/(運算符):需要依次彈出棧中優先級高於或等於當前運算符(/)的所有運算符加入后綴表達式,若遇到“(”或棧空則停止彈出-----由於棧頂元素“-”優先級小於當前運算符,不彈棧頂元素。最后將當前運算符“/”入棧
⑬掃描F(操作數):直接加入后綴表達式:ABCD-*+EF
⑭掃描完所有元素后,依次彈出棧內剩余元素,加入后綴表達式得:ABCD-*+EF/-
六、終極版--中綴表達式的計算(機算)
以上實現了中綴表達式轉后綴表達式的機算方式、后綴表達式的機算,下面就是將以上兩個這合並-------中綴表達式的機算
思想:
(1)初始化兩個棧,操作數棧和運算符棧
(2)若掃描到操作數,壓入操作數棧
(3)若掃描到運算符或界限符,則按照“中綴轉后綴”思想壓入運算符棧(期間會彈出運算符,每當彈出一個運算符時,就需要彈出兩個操作數棧的棧頂元素並執行響應運算,運算結果再壓回操作數棧)
(4)最終運算結果就是操作數棧中的唯一一個元素
最終代碼實現:

import java.math.BigDecimal; import java.util.*; import java.util.regex.Pattern; /** * @ClassName: StackDemo1 * @Description: * @Author: xiedong * @Date: 2020/9/16 11:51 */ public class StackDemo2 { public static final String regexNumber = "^-?\\d+(\\.\\d+)?$";//數字正則 private static Map<String, Integer> orderMap = new HashMap(); private static final Integer errorNum = Integer.MAX_VALUE; static { orderMap.put("+", 0); orderMap.put("-", 0); orderMap.put("*", 1); orderMap.put("/", 1); orderMap.put("(", 2); orderMap.put(")", 3); } public static void main(String[] args) { //處理數據源 String source = "((15/(7-(1+1)))*3)-(2+(1+1))"; //逆波蘭表達式 StringBuilder suffixExpression = new StringBuilder(); //操作數棧 Stack<String> operationStack = new Stack<>(); //運算符棧 Stack<String> arithmeticStack = new Stack<>(); //處理源 getOpLists(source).stream().forEach(per -> { //獲取掃描對應優先級 Integer val = orderMap.getOrDefault(per, errorNum); if (Pattern.compile(regexNumber).matcher(per).matches()) { //如果是操作數則直接壓入操作數棧 operationStack.push(per); //直接加入后綴表達式 suffixExpression.append(per); } else if (val <= 1) { //如果是運算符:+-*/ if (arithmeticStack.isEmpty()) //棧空則直接入棧 arithmeticStack.push(per); else { //查看當前棧頂元素的優先級 int peekNum = orderMap.get(arithmeticStack.peek()); //當棧頂運算符優先級高於或等於當前運算符的優先級且棧頂元素不為左括號( while (peekNum >= orderMap.get(per) && peekNum <= 1) { //依次彈出棧頂元素--即運算符 String pop = arithmeticStack.pop(); //彈出操作數棧棧頂元素---對應右操作數 String rightOperNum = operationStack.pop(); //彈出操作數棧棧頂元素---對應右操作數 String leftOperNum = operationStack.pop(); //加入后綴表達式(逆波蘭表達式) suffixExpression.append(pop); //左右操作數執行對應運算 String tmpResult = doCalculate(pop, leftOperNum, rightOperNum); //運算結果壓回操作數棧 operationStack.push(tmpResult); //查看當前棧頂元素的優先級 peekNum = orderMap.get(arithmeticStack.peek()); } //最后當前運算符壓入運算符棧 arithmeticStack.push(per); } } else if (val == 2) //左括號直接壓入運算符棧 arithmeticStack.push(per); else if (val == 3) {//右括號 String pop = arithmeticStack.pop(); Integer popMapNum = orderMap.get(pop); //遇到右括號則依次彈出運算符棧元素,直到彈出左括號"("為止 while (popMapNum != 2) { //每次彈出一個 運算符,就必須彈出兩個操作數執行對應運算,並將結果壓回操作數棧中 String rightOperNum = operationStack.pop(); String leftOperNum = operationStack.pop(); //彈出的運算符加入逆波蘭表達式 suffixExpression.append(pop); //左右操作數執行對應運算 String tmpResult = doCalculate(pop, leftOperNum, rightOperNum); //運算結果壓回操作數棧 operationStack.push(tmpResult); //再次彈出運算符棧---查看是否為左括號"(" pop = arithmeticStack.pop(); popMapNum = orderMap.get(pop); } } } ); //當掃描完所有字符后,依次彈出運算符棧元素,期間每彈出一個運算符,就需要彈出兩個操作數進行相應運算,並將結果壓回操作數棧 while (!arithmeticStack.isEmpty()) { //依次彈出運算符棧元素 String pop = arithmeticStack.pop(); //加入逆波蘭表達式 suffixExpression.append(pop); //期間每彈出一個運算符,就需要彈出兩個操作數進行相應運算 String rightNum = operationStack.pop(); String leftNum = operationStack.pop(); String tmpResult = doCalculate(pop, leftNum, rightNum); //運算結果壓回操作數棧中 operationStack.push(tmpResult); } System.out.println("結果是:" + operationStack.peek()); System.out.println("逆波蘭表達式:" + suffixExpression.toString()); } /** * 分割源字符串 * * @param source 源字符串 * @return */ private static List<String> getOpLists(String source) { ArrayList<String> res = new ArrayList<>(); char[] charArray = source.toCharArray(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < charArray.length; i++) { sb.append(charArray[i]); if (Character.isDigit(charArray[i]) && i != charArray.length - 1 && Character.isDigit(charArray[i + 1])) { continue; } else { res.add(sb.toString()); sb.delete(0, sb.length()); } } return res; } /** * 按對應運算符執行運算 * * @param pop 運算符 * @param leftOperNum 左操作數 * @param rightOperNum 右操作數 * @return */ private static String doCalculate(String pop, String leftOperNum, String rightOperNum) { BigDecimal res = BigDecimal.ZERO; switch (pop) { case "+": res = new BigDecimal(leftOperNum).add(new BigDecimal(rightOperNum)); break; case "-": res = new BigDecimal(leftOperNum).subtract(new BigDecimal(rightOperNum)); break; case "*": res = new BigDecimal(leftOperNum).multiply(new BigDecimal(rightOperNum)); break; case "/": res = new BigDecimal(leftOperNum).divide(new BigDecimal(rightOperNum)); break; } return res.toString(); } }