1、程序要求java
解析通常數學算式,實現簡單的帶括號的加減乘除運算。算法
2、基本思路數組
前面兩篇介紹了直接解析字符串和用數組容器輔助解析的兩種方式,此次再介紹最經常使用的解析算法——解析後綴表達式(逆波蘭表達式)。app
3、逆波蘭表達式及其獲得算法測試
一、逆波蘭表達式spa
也即後綴表達式,指的是不包含括號,運算符放在兩個運算對象的後面,全部的計算按運算符出現的順序,嚴格從左向右進行(再也不考慮運算符的優先規則)。(摘自百度),既然沒了運算符的優先規則,那麼計算機解析起來天然容易的多。code
對於咱們常見的表達式,稱爲中綴表達式,每一箇中綴表達式都有對應的後綴表達式。如: 對象
中綴表達式:-2*(1+6/3)+4 blog
後綴表達式:-2 1 6 3 / + * 4 +(這裏爲了區分負號和減號,我在數字與數字、數字與符號之間都加了空格,至於怎麼從中綴表達式獲得後綴表達式,後面有介紹及參考程序)ip
而在解析後綴表達式時,只須要遵照如下原則便可:
從左往右遍歷
遇到數字直接放入容器
遇到運算符,將最後兩個數字取出,進行該運算,將結果再放入容器
遍歷結束後,容器中的數字即爲運算結果
按這個過程走下來,天然而然的想到用棧是最合適的。
現只需想辦法由輸入的中綴表達式轉爲後綴表達式便可完成解析。
二、由中綴表達式獲得後綴表達式的算法
由中綴表達式獲得後綴表達式,只要遵照如下步驟便可:
首先設置運算符的優先級(這樣設置也是爲了簡化程序):
」null」 棧頂若爲空,假設優先級爲0
「(」 優先級設爲1
「+-」 優先級設爲2
「*/」 優先級設爲3
從左向右遍歷中綴表達式
遇到數字直接輸出
遇到符號
遇到左括號,直接壓棧
遇到右括號,彈棧輸出直到彈出左括號(左括號不輸出)
遇到運算符,比較棧頂符號,若該運算符優先級大於棧頂,直接壓棧;若小於棧頂,彈棧輸出直到大於棧頂,而後將改運算符壓棧。
最後將符合棧彈棧並輸出
現根據這個原則,手動模擬一遍轉換過程:
仍是以-2*(1+6/3)+4爲例
環境:
先寫一個最基本的兩位數四則運算方法,比較簡單,沒有寫註釋:
private static double doubleCal(double a1, double a2, char operator) throws Exception { switch (operator) { case '+': return a1 + a2; case '-': return a1 - a2; case '*': return a1 * a2; case '/': return a1 / a2; default: break; } throw new Exception("illegal operator!"); }
寫一個得到優先級的方法:
private static int getPriority(String s) throws Exception { if(s==null) return 0; switch(s) { case "(":return 1; case "+":; case "-":return 2; case "*":; case "/":return 3; default:break; } throw new Exception("illegal operator!"); }
將中綴表達式轉變爲後綴表達式:
private static String toSufExpr(String expr) throws Exception { System.out.println("將"+expr+"解析爲後綴表達式..."); /*返回結果字符串*/ StringBuffer sufExpr = new StringBuffer(); /*盛放運算符的棧*/ Stack<String> operator = new Stack<String>(); operator.push(null);//在棧頂壓人一個null,配合它的優先級,目的是減小下面程序的判斷 /* 將expr打散分散成運算數和運算符 */ Pattern p = Pattern.compile("(?<!\\d)-?\\d+(\\.\\d+)?|[+\\-*/()]");//這個正則爲匹配表達式中的數字或運算符 Matcher m = p.matcher(expr); while (m.find()) { String temp = m.group(); if (temp.matches("[+\\-*/()]")) { //是運算符 if (temp.equals("(")) { //遇到左括號,直接壓棧 operator.push(temp); System.out.println("'('壓棧"); } else if (temp.equals(")")) { //遇到右括號,彈棧輸出直到彈出左括號(左括號不輸出) String topItem = null; while (!(topItem = operator.pop()).equals("(")) { System.out.println(topItem+"彈棧"); sufExpr.append(topItem+" "); System.out.println("輸出:"+sufExpr); } } else {//遇到運算符,比較棧頂符號,若該運算符優先級大於棧頂,直接壓棧;若小於棧頂,彈棧輸出直到大於棧頂,而後將改運算符壓棧。 while(getPriority(temp) <= getPriority(operator.peek())) { sufExpr.append(operator.pop()+" "); System.out.println("輸出sufExpr:"+sufExpr); } operator.push(temp); System.out.println("\""+temp+"\""+"壓棧"); } }else {//遇到數字直接輸出 sufExpr.append(temp+" "); System.out.println("輸出sufExpr:"+sufExpr); } } String topItem = null;//最後將符合棧彈棧並輸出 while(null != (topItem = operator.pop())) { sufExpr.append(topItem+" "); } return sufExpr.toString(); }
解析中綴表達式的方法:
public static String getResult(String expr) throws Exception { String sufExpr = toSufExpr(expr);// 轉爲後綴表達式 System.out.println("開始計算後綴表達式..."); /* 盛放數字棧 */ Stack<Double> number = new Stack<Double>(); /* 這個正則匹配每一個數字和符號 */ Pattern p = Pattern.compile("-?\\d+(\\.\\d+)?|[+\\-*/]"); Matcher m = p.matcher(sufExpr); while (m.find()) { String temp = m.group(); if (temp.matches("[+\\-*/]")) {// 遇到運算符,將最後兩個數字取出,進行該運算,將結果再放入容器 System.out.println("符號"+temp); double a1 = number.pop(); double a2 = number.pop(); double res = doubleCal(a2, a1, temp.charAt(0)); number.push(res); System.out.println(a2 + "和" + a1 + "彈棧,並計算" + a2 + temp + a1); System.out.println("數字棧:" + number); } else {// 遇到數字直接放入容器 number.push(Double.valueOf(temp)); System.out.println("數字棧:" + number); } } return number.pop() + ""; }
主方法,以-3.5*(4.5-(4+(-1-1/2)))測試
public static void main(String[] args) throws Exception { String str = "-3.5*(4.5-(4+(-1-1/2)))"; System.out.println(getResult(str)); }
6、簡化過程分析
根據這個算法,在不須要解出後綴表達式的狀況下,還能夠將代碼進一步簡化。
在解析的過程的中,咱們只須要按照如下原則:
使用兩個棧,一個數字棧,一個符號棧
從左往右遍歷表達式字符串
遇到數字,直接壓入數字棧
遇到符號
遇到左括號,直接入符號棧
遇到右括號,」符號棧彈棧取棧頂符號b,數字棧彈棧取棧頂數字a1,數字棧彈棧取棧頂數字a2,計算a2 b a1 ,將結果壓入數字棧」,重複引號步驟至取棧頂爲左括號,將左括號彈出
遇到運算符,1)若該運算符的優先級大於棧頂元素的優先級,直接入符號棧。2)若小於,」符號棧彈棧取棧頂符號b,數字棧彈棧取棧頂數字a1,數字棧彈棧取棧頂數字a2,計算a2 b a1 ,將結果壓入數字棧」,重複引號步驟至該運算符的優先級大於符號棧頂元素的優先級,而後將該符號入符號棧
遍歷結束後,」符號棧彈棧取棧頂符號b,數字棧彈棧取棧頂數字a1,數字棧彈棧取棧頂數字a2,計算a2 b a1 ,將結果壓入數字棧」,重複引號步驟至符號棧無符號(或數字棧只有一個元素),則數字棧的元素爲運算結果
7、代碼二
環境:
Eclipse Java EE IDE(Version: Oxygen.1a Release (4.7.1a))
jdk1.8.0_131
先寫一個最基本的兩位數四則運算方法,比較簡單,沒有寫註釋:
private static double doubleCal(double a1, double a2, char operator) throws Exception { switch (operator) { case '+': return a1 + a2; case '-': return a1 - a2; case '*': return a1 * a2; case '/': return a1 / a2; default: break; } throw new Exception("illegal operator!"); }
寫一個得到優先級的方法:
private static int getPriority(String s) throws Exception { if(s==null) return 0; switch(s) { case "(":return 1; case "+":; case "-":return 2; case "*":; case "/":return 3; default:break; } throw new Exception("illegal operator!"); }
解析表達式:
public static String getResult(String expr) throws Exception { System.out.println("計算"+expr); /*數字棧*/ Stack<Double> number = new Stack<Double>(); /*符號棧*/ Stack<String> operator = new Stack<String>(); operator.push(null);// 在棧頂壓人一個null,配合它的優先級,目的是減小下面程序的判斷 /* 將expr打散爲運算數和運算符 */ Pattern p = Pattern.compile("(?<!\\d)-?\\d+(\\.\\d+)?|[+\\-*/()]");// 這個正則爲匹配表達式中的數字或運算符 Matcher m = p.matcher(expr); while(m.find()) { String temp = m.group(); if(temp.matches("[+\\-*/()]")) {//遇到符號 if(temp.equals("(")) {//遇到左括號,直接入符號棧 operator.push(temp); System.out.println("符號棧更新:"+operator); }else if(temp.equals(")")){//遇到右括號,"符號棧彈棧取棧頂符號b,數字棧彈棧取棧頂數字a1,數字棧彈棧取棧頂數字a2,計算a2 b a1 ,將結果壓入數字棧",重複引號步驟至取棧頂爲左括號,將左括號彈出 String b = null; while(!(b = operator.pop()).equals("(")) { System.out.println("符號棧更新:"+operator); double a1 = number.pop(); double a2 = number.pop(); System.out.println("數字棧更新:"+number); System.out.println("計算"+a2+b+a1); number.push(doubleCal(a2, a1, b.charAt(0))); System.out.println("數字棧更新:"+number); } System.out.println("符號棧更新:"+operator); }else {//遇到運算符,知足該運算符的優先級大於棧頂元素的優先級壓棧;不然計算後壓棧 while(getPriority(temp) <= getPriority(operator.peek())) { double a1 = number.pop(); double a2 = number.pop(); String b = operator.pop(); System.out.println("符號棧更新:"+operator); System.out.println("數字棧更新:"+number); System.out.println("計算"+a2+b+a1); number.push(doubleCal(a2, a1, b.charAt(0))); System.out.println("數字棧更新:"+number); } operator.push(temp); System.out.println("符號棧更新:"+operator); } }else {//遇到數字,直接壓入數字棧 number.push(Double.valueOf(temp)); System.out.println("數字棧更新:"+number); } } while(operator.peek()!=null) {//遍歷結束後,符號棧數字棧依次彈棧計算,並將結果壓入數字棧 double a1 = number.pop(); double a2 = number.pop(); String b = operator.pop(); System.out.println("符號棧更新:"+operator); System.out.println("數字棧更新:"+number); System.out.println("計算"+a2+b+a1); number.push(doubleCal(a2, a1, b.charAt(0))); System.out.println("數字棧更新:"+number); } return number.pop()+""; }
主方法,以-3.5*(4.5-(4+(-1-1/2)))測試
public static void main(String[] args) throws Exception { String str = "-3.5*(4.5-(4+(-1-1/2)))"; System.out.println(getResult(str)); }