棧實現綜合計算器、逆波蘭計算器

對於一個字符串類型的表達式,實現基礎的加減乘除運算

String expression = "8/2+2*5-4";

使用棧來實現這個表達式的解析和運算

實現思路:

1,通過一個index來遍歷我們的表達式

2,如果發現是一個數字,判斷下一位是不是運算符,如果是,把數字入數棧。如果不是,繼續掃描,拼接數字

3,如果發現是一個運算符,判斷符號棧是否爲空,如果爲空,直接入符號棧。如果不爲空,就跟符號棧中棧頂的符號進行比較,如果優先級小於等於棧頂的符號,就需要從數棧中pop出兩個數,從符號棧中pop出一個符號進行運算(注意運算時候的順序,在進行減和除的時候,後面出來的數在前),將得到的結果入數棧,將當前掃描到的符號入符號棧。如果當前符號的優先級大於符號棧中棧頂的符號,就直接把當前符號入符號棧。

4,當表達式掃描完畢,就順序的從數棧和符號棧中pop出相應的數和符號,進行運算。

5,最後數棧中只有一個數,就是表達式的結果。

代碼實現:

public class StackCompute {

    @Test
    public void main(){

        //表達式
        String expression = "8/2+2*5-4";
        //創建兩個棧,一個數棧,一個符號棧
        Stack<Integer> numStack = new Stack<>();
        Stack<Character> operStack = new Stack<>();

        //定義需要的變量
        int index = 0;//用於掃描表達式
        int num1 = 0;
        int num2 = 0;
        int oper = 0;//符號
        int result = 0;//運算結果
        char ch = ' ';//將每次掃描到char保存到ch
        String keepNum = "";//拼接數

        //開始while循環掃描表達式
        while(true){
            //依次得到表達式中的每一個字符
            ch = expression.substring(index,index+1).charAt(0);
            //判斷ch是什麼,然後做相應的處理
            if(isOper(ch)){//如果是一個運算符
                //判斷當前符號棧是否爲空
                if(!operStack.isEmpty()){
                    /*
                        如果符號棧有操作符,就進行比較,如果當前的操作符的優先級小於或者等於棧中的操作符,就需要從數棧中pop出兩個數
                        在從符號棧中pop出一個符號,進行運算,將得到結果,入數棧,然後將當前掃描到的操作符入符號棧
                     */
                    if (priority(ch) <= priority(operStack.peek())) {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        result = cal(num1,num2,oper);
                        //把運算結果入數棧
                        numStack.push(result);
                        //當前符號入符號棧
                        operStack.push(ch);
                    }else{
                        //如果當前符號的優先級大於棧中符號的優先級,就直接入符號棧
                        operStack.push(ch);
                    }
                }else{
                    //如果爲空,直接入棧
                    operStack.push(ch);
                }
            }else{
                //如果是數,入數棧
                //處理多位數
                keepNum += ch;
                //如果是表達式的最後一位。直接入數棧
                if(index == expression.length()-1){
                    numStack.push(Integer.parseInt(keepNum));
                }else{
                    //如果不是最後一位,則判斷後一位是不是字符,如果是字符,則入棧,反之,則繼續掃描
                    if(isOper(expression.substring(index+1,index+2).charAt(0))){
                        numStack.push(Integer.parseInt(keepNum));
                        keepNum = "";//清空keepNum
                    }
                }
            }
            index++;
            if(index >= expression.length()){
                break;
            }
        }

        //當表達式掃描完畢,就順序的從數棧和符號棧中pop出相應的數和符號,並運行。
        while(true){
            //如果符號棧爲空,表示已經計算到最後的結果,數棧中此時應該只有一個數字,就是結果
            if(operStack.isEmpty()){
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            result = cal(num1,num2,oper);
            //把運算結果入數棧
            numStack.push(result);
        }

        System.out.println(numStack.pop());
    }

    //判斷是否是一個運算符,目前僅限於加減乘除
    boolean isOper(int ch){
        return ch == '+' || ch == '-' || ch == '*' || ch == '/';
    }

    //判斷優先級
    int priority(int ch){
        if(ch == '*' || ch == '/'){
            return 1;
        }else{
            return 0;
        }
    }

    //計算方法
    int cal(int num1,int num2,int oper){
        int result = 0;
        switch (oper){
            case '+': result = num1 + num2;break;
            case '-': result = num2 - num1;break;//注意順序,後出來的數做被減數
            case '*': result = num1 * num2;break;
            case '/': result = num2 / num1;break;//注意順序,後出來的數做被除數
        }

        return result;
    }
}

在計算機中,前綴表達式(波蘭表達式)或者後綴表達式(逆波蘭表達式)比中綴表達式更好解析。

前綴表達式

 後綴表達式

 後綴表達式的求值:

public class PolandNotation {

    public static void main(String[] args) {
        //定義一個逆波蘭表達式
        //5*7-8+3+9/3  =>  5 7 * 8 - 3 + 9 3 / +
        //爲了方便,我們吧逆波蘭表達式的數字和符號之間使用空格隔開
        String suffixExpression = "5 7 * 8 - 3 + 9 3 / +";

        /**
         * 思路
         * 1,先將表達式放入一個ArrayList中
         * 2,遍歷ArrayList配合棧完成計算
         */
        List<String> list = getListString(suffixExpression);
        System.out.println(calculate(list));
    }

    //將表達式轉換成集合
    public static List<String> getListString(String suffixExpression) {
        List<String> list = new ArrayList<String>();
        String[] split = suffixExpression.split(" ");
        for (String ele : split) {
            list.add(ele);
        }
        return list;
    }

    /**
     * 完成對逆波蘭表達式的計算
     * 思路分析:
     * 1,從左至右掃描,將3和4壓入堆棧;
     * 2,遇到+運算符,因此彈出4和3 ( 4爲棧頂元素, 3爲次項元素),計算出3+4的值,得7,再將7入棧;
     * 3,將5入棧;
     * 4,接下來是*運算符,因此彈出5和7,計算出7*5=35,將35入棧;
     * 5,將6入棧;
     * 6,最後是-運算符,計算出35-6的值,即29,由此得出最終結果;
     *
     * @param ls
     * @return
     */
    public static int calculate(List<String> ls) {

        //創建一個棧
        Stack<String> stack = new Stack<>();

        for (String item : ls) {
            if (item.matches("\\d+")) {//如果是數字
                stack.push(item);//入棧
            } else {
                //pop出兩個數進行運算後再入棧
                //因爲需要用後面彈出的數做被減數或被除數,所以爲了方便,這裏第一個彈出的數命名爲num2
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int result = 0;
                if (item.equals("+")) {
                    result = num1 + num2;
                } else if (item.equals("-")) {
                    result = num1 - num2;
                } else if (item.equals("/")) {
                    result = num1 / num2;
                } else if (item.equals("*")) {
                    result = num1 * num2;
                }else{
                    throw new RuntimeException("運算符有誤");
                }
                //把結果入棧
                stack.push(result+"");
            }
        }
        //最後留在棧中的數就是運算結果
        return Integer.parseInt(stack.pop());
    }
}

那麼問題來了,如何把一箇中綴表達式轉成一個後綴表達式呢?

思路分析:

1,初始化兩個棧,運算符棧s1和存儲中間結果棧s2

2,從左至右掃描中綴表達式

3,遇到數字時,將其壓入s2

4,遇到運算符時,比較其與s1棧頂運算符的優先級

    4.1,如果s1爲空,或棧頂運算符爲左括號,則直接將此運算符入s1

    4.2,如果運算符比棧頂的運算符優先級高,也將運算符入s1

    4.3,如果運算符比棧頂的運算符優先級低,將s1的運算符彈出壓入s2,再次轉到4.1中與新的棧頂運算符比較

5,遇到括號時

    5.1,如果是左括號,直接壓入s1

    5.2,如果是右括號,則依次彈出s1棧頂的運算符壓入s2,直到遇到左括號爲止,此時將這一對括號丟棄

6,重複步驟2至5,直到表達式的最右邊

7,將s1中剩餘的運算符依次彈出並壓入s2,

8,依次彈出s2中的元素並輸出,結果的逆序即爲中綴表達式對應的後綴表達式

代碼實現:

    public static void main(String[] args) {

        List<String> list = toInfixExpressionList("1+((2+3)*4)-5");
        System.out.println(list);
        
        List<String> result = parseSuffixExpreesionList(list);
        System.out.println(result);
    }


    /**
     * 將中綴表達式轉換成對應的list
     *
     * @param s
     * @return
     */
    public static List<String> toInfixExpressionList(String s) {
        //定義一個list,存放中綴表達式對應的內容
        List<String> list = new ArrayList<>();
        int i = 0;//這是一個指針,用於遍歷中綴表達式字符串
        String str;//對多位數的拼接
        char c;//每遍歷到一個字符就放入到c中

        do {
            //如果c是一個非數字,加入到list中  0-9對應的Asall碼是48-57
            if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
                list.add(c + "");
                i++;
            } else {//如果是一個數字
                str = "";
                while (i < s.length() && (c = s.charAt(i)) > 48 && (c = s.charAt(i)) < 57) {
                    str += c;//拼接多位數
                    i++;
                }
                list.add(str);
            }
        } while (i < s.length());

        return list;
    }

    /**
     * 中綴表達式的list轉後綴表達式list
     *
     * @param list
     * @return
     */
    public static List<String> parseSuffixExpreesionList(List<String> list) {

        //定義兩個棧
        Stack<String> s1 = new Stack<>();//符號棧

        /**
         * 因爲s2這個棧,在整個轉換過程中,沒有pop操作,而且後面還要逆序輸出
         * 就比較麻煩,所以使用ArrayList代替
         */
        List<String> s2 = new ArrayList<>();//存儲中間結果的list

        //遍歷list
        for (String item : list) {
            //如果是一個數字,加入s2
            if (item.matches("\\d+")) {
                s2.add(item);
            } else if (item.equals("(")) {
                s1.push(item);
            }else if(item.equals(")")){
                //如果是右括號,則依次彈出s1棧頂的運算符壓入s2,直到遇到左括號爲止,此時將這一對括號丟棄
                while(!s1.peek().equals("(")){
                    s2.add(s1.pop());
                }
                s1.pop();//將左括號彈出,消除一堆小括號
            }else{
                //如果運算符比棧頂的運算符優先級低,將s1的運算符彈出壓入s2,再次轉到4.1中與新的棧頂運算符比較
                while(s1.size() != 0 && getValue(s1.peek()) >= getValue(item)){
                    s2.add(s1.pop());
                }
                //然後將item入棧
                s1.push(item);
            }
        }

        //將s1剩餘的運算符依次彈出並加入s2
        while(s1.size() != 0){
            s2.add(s1.pop());
        }

        return s2;
    }

    /**
     * 判斷運算符優先級
     * @param oper
     * @return
     */
    public static int getValue(String oper){
        int result = 0;
        switch (oper){
            case "+" :
                result = 1;
                break;
            case "-" :
                result = 1;
                break;
            case "*" :
                result = 2;
                break;
            case "/":
                result = 2;
                break;
            default:
                System.out.println("不存在或不支持該運算符");
                break;
        }
        return result;
    }

如果需要加入小數點,可以把運算時候的數據類型改成Double,然後加入判斷小數點

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章