JAVA四則運算算法

 

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爲例 

 

 

 

 

 

4、代碼一

環境:

  • 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!");
    }

將中綴表達式轉變爲後綴表達式:

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));
    }

5、執行結果

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));
    }

相關文章
相關標籤/搜索