資料結構與演算法【Java】03---棧

語言: CN / TW / HK

前言

資料 data 結構(structure)是一門 研究組織資料方式的學科,有了程式語言也就有了資料結構.學好資料結構才可以編寫出更加漂亮,更加有效率的程式碼。

  • 要學習好資料結構就要多多考慮如何將生活中遇到的問題,用程式去實現解決.
  • 程式 = 資料結構 + 演算法
  • 資料結構是演算法的基礎, 換言之,想要學好演算法,需要把資料結構學到位

我會用資料結構與演算法【Java】這一系列的部落格記錄自己的學習過程,如有遺留和錯誤歡迎大家提出,我會第一時間改正!!!

注:資料結構與演算法【Java】這一系列的部落格參考於B站尚矽谷的影片,影片原地址為 【尚矽谷】資料結構與演算法(Java資料結構與演算法)

上一篇文章 資料結構與演算法【Java】02---連結串列

接下來進入正題!

1、棧的簡介

  1. 棧的英文為(stack)
  2. 棧是一個 先入後出(FILO-First In Last Out) 的有序列表。
  3. 棧(stack) 是限制線性表中元素的插入和刪除只能在線性表的同一端進行的一種特殊線性表。允許插入和刪除的一端,為 變化的一端,稱為棧頂(Top) ,另一端為 固定的一端,稱為棧底(Bottom) 。
  4. 根據棧的定義可知 , 最先放入棧中元素在棧底 , 最後放入的元素在棧頂 , 而刪除元素剛好相反 , 最後放入的元素最先刪除,最先放入的元素最後刪除
  5. 圖解方式說明出棧(pop) 和入棧(push)
  • 入棧

  • 出棧

2、棧的應用場景

  1. 子程式的呼叫:在跳往子程式前,會先將下個指令的地址存到堆疊中,直到子程式執行完後再將地址取出,以回到原來的程式中。
  2. 處理遞迴呼叫:和子程式的呼叫類似,只是除了儲存下一個指令的地址外,也將引數、區域變數等資料存入堆疊中。
  3. 表示式的轉換[中綴表示式轉字尾表示式]與求值(實際解決)。
  4. 二叉樹的遍歷。
  5. 圖形的深度優先(depth 一 first)搜尋法。

3、棧的快速入門

陣列模擬棧

  • 思路分析

  • 程式碼實現

    public class ArrayStackDemo {
        public static void main(String[] args) {
            //測試ArrayStack
            //先建立一個ArrayStack的物件,表示一個棧
            ArrayStack stack = new ArrayStack(4);
            String key = "";
            boolean loop = true;//控制是否退出
            Scanner scanner = new Scanner(System.in);
            while (loop){
                System.out.println("show:顯示棧");
                System.out.println("exit:退出程式");
                System.out.println("push:新增資料到棧");
                System.out.println("pop:從棧取出資料");
                System.out.println("請輸入你的選擇");
                key = scanner.next();
                switch (key){
                    case "show":
                        stack.list();
                        break;
                    case "exit":
                        scanner.close();
                        loop = false;
                        break;
                    case "push":
                        System.out.println("請輸入一個數");
                        int value = scanner.nextInt();
                        stack.push(value);
                        break;
                    case "pop":
                        try {
                            int res = stack.pop();
                            System.out.println("出棧的資料是:"+res);
                        }catch (Exception e){
                            System.out.println(e.getMessage());
                        }
                        break;
                    default:
                        break;
                }
            }
            System.out.println("程式退出");
    
        }
    }
    
    
    
    
    //定義一個ArrayStack表示棧
    class ArrayStack{
        private int maxSize;//棧的大小
        private int[] stack;//陣列,陣列模擬棧,資料就放在陣列中
        private int top = -1;//top表示棧頂,初始化為-1
    
        //構造器
        public ArrayStack(int maxSize) {
            this.maxSize = maxSize;
            stack = new int[this.maxSize];
        }
    
        //判斷棧滿
        public boolean isFull(){
            return top == maxSize - 1;//陣列下標從0開始,棧的最後一個下標是maxSize - 1
        }
    
        //判斷棧空
        public boolean isEmpty(){
            return top == -1;
        }
    
        //入棧
        public void push(int value){
            //先判斷棧是否滿
            if(isFull()){
                System.out.println("棧滿");
                return;
            }
            top++;
            stack[top] = value;
        }
    
        //出棧,將棧頂的資料返回
        public int pop(){
            //先判斷棧是否空
            if(isEmpty()){
                //丟擲異常
                throw  new RuntimeException("棧空");
            }
            int value = stack[top];
            top--;
            return value;
    
        }
    
    
        //顯示棧(遍歷),需要從棧頂開始顯示資料
        public void list(){
            if(isEmpty()){
                System.out.println("棧空");
                return;
            }
            for (int i = top; i >= 0; i--) {
                System.out.printf("stack[%d]=%d\n",i,stack[i]);
            }
        }
    
    
    }
  • 結果展示

4、中綴表示式計算

  • 需求分析

    • 計算中綴表示式:7*1+6/3-4
  • 思路分析

  • 程式碼實現

    public class Calculator {
         public static void main(String[] args) {
             //測試表達式的運算
             String expression = "7*1+6/3-4";
             //建立兩個棧,一個數棧,一個符號棧
             ArrayStack2 numStack = new ArrayStack2(10);
             ArrayStack2 operStack = new ArrayStack2(10);
             //定義需要的相關變數
             int index = 0;//用於掃描
             int num1 = 0;
             int num2 = 0;
             int oper = 0;
             int res = 0;
             char ch = ' ';//將每次掃描得到的char儲存到ch
             String keepNum = "";//用於拼接多位數
             //用while迴圈掃描expression
             while (true){
                 //一次得到expression的每一個字元
                 ch = expression.substring(index,index+1).charAt(0);
                 //判斷ch 是什麼做相應的處理
                 if(operStack.isOper(ch)){
                     //如果是運算子
                     //判斷當前的符號棧是否為空
                     if(!operStack.isEmpty()){
                         //處理
                         //如果符號棧有操作符,就進行比較,如果當前的操作符的優先順序小於或者等於棧中的操作符,就需要從數棧中pop出兩個數,
                         //在從符號棧中pop出一個符號,進行運算,將得到結果,入數棧,然後將當前的操作符入符號棧
                         if(operStack.priority(ch) <= operStack.priority(operStack.peek())){
                             num1 = numStack.pop();
                             num2 = numStack.pop();
                             oper = operStack.pop();
                             res = numStack.cal(num1,num2,oper);
                             //把運算結果入棧
                             numStack.push(res);
                             //將當前的操作符入符號棧
                             operStack.push(ch);
                         }else {
                             ////如果當前的操作符的優先順序大於棧中的操作符, 就直接入符號棧.
                             operStack.push(ch);
                         }
                     }else {
                         //如果為空,直接入符號棧
                         operStack.push(ch);
     
                     }
                 }else {
                     //如果是數,直接入數棧
                     //numStack.push(ch - 48);//-48  數字對應ascii碼錶
                     //分析思路
                     //1. 當處理多位數時,不能發現是一個數就立即入棧,因為他可能是多位數
                     //2. 在處理數,需要向expression的表示式的index 後再看一位,如果是數就進行掃描,如果是符號才入棧
                     //3. 因此我們需要定義一個變數 字串,用於拼接
     
                     //處理多位數
                     keepNum += ch;
     
     
                     //如果ch已經是expression的最後一位,就直接入棧
                     if(index == expression.length()-1){
                         numStack.push(Integer.parseInt(keepNum));
                     }else {
                         //判斷下一個字元是不是數字,如果是數字就繼續掃描,如果是運算子就入棧
                         //注意是後看一位,不是index++
                         if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
                             //如果後一位是運算子就入棧
                             numStack.push(Integer.parseInt(keepNum));
                             //!!!!!!!!,keepNum清空
                             keepNum = "";
                         }
                     }
     
                 }
                 //讓index加1,並判斷是否掃描到了expression的最後
                 index++;
                 if(index >= expression.length()){
                     break;
                 }
             }
             //當表示式掃描完畢,就順序的從 數棧和符號棧中pop出相應的數和符號,並執行.
             while(true) {
                 //如果符號棧為空,則計算到最後的結果, 數棧中只有一個數字【結果】
                 if(operStack.isEmpty()) {
                     break;
                 }
                 num1 = numStack.pop();
                 num2 = numStack.pop();
                 oper = operStack.pop();
                 res = numStack.cal(num1, num2, oper);
                 numStack.push(res);//入棧
             }
             //將數棧的最後數,pop出,就是結果
             int res2 = numStack.pop();
             System.out.printf("表示式 %s = %d", expression, res2);
     
         }
     }
     
     
     
     
     //先建立一個棧,這裡直接使用前面建立好的
     //定義一個ArrayStack表示棧,需要擴充套件功能
     class ArrayStack2{
         private int maxSize;//棧的大小
         private int[] stack;//陣列,陣列模擬棧,資料就放在陣列中
         private int top = -1;//top表示棧頂,初始化為-1
     
         //構造器
         public ArrayStack2(int maxSize) {
             this.maxSize = maxSize;
             stack = new int[this.maxSize];
         }
     
         //增加一個方法,可以返回當前棧頂的值, 但是不是出棧
         public int peek() {
             return stack[top];
         }
     
         //判斷棧滿
         public boolean isFull(){
             return top == maxSize - 1;//陣列下標從0開始,棧的最後一個下標是maxSize - 1
         }
     
         //判斷棧空
         public boolean isEmpty(){
             return top == -1;
         }
     
         //入棧
         public void push(int value){
             //先判斷棧是否滿
             if(isFull()){
                 System.out.println("棧滿");
                 return;
             }
             top++;
             stack[top] = value;
         }
     
         //出棧,將棧頂的資料返回
         public int pop(){
             //先判斷棧是否空
             if(isEmpty()){
                 //丟擲異常
                 throw  new RuntimeException("棧空");
             }
             int value = stack[top];
             top--;
             return value;
     
         }
     
     
         //顯示棧(遍歷),需要從棧頂開始顯示資料
         public void list(){
             if(isEmpty()){
                 System.out.println("棧空");
                 return;
             }
             for (int i = top; i >= 0; i--) {
                 System.out.printf("stack[%d]=%d\n",i,stack[i]);
             }
         }
     
     
         //返回運算子的優先順序,優先順序是程式設計師來確定, 優先順序使用數字表示
         //數字越大,則優先順序就越高.
         public int priority(int oper){
             if(oper == '*' || oper == '/'){
                 return 1;
             }else if(oper == '+' || oper == '-'){
                 return 0;
             }else {
                 return -1;//假設只有+ — * /
             }
         }
     
     
         //判斷是不是一個運算子
         public boolean isOper(char val){
             return val == '+' || val == '-' || val == '*' || val == '/';
         }
     
         //計算方法
         public int cal(int num1,int num2,int oper){
             int res = 0;//用於存放計算的結果
             switch (oper) {
                 case '+':
                     res = num1 + num2;
                     break;
                 case '-':
                     res = num2 - num1;// 注意順序
                     break;
                 case '*':
                     res = num1 * num2;
                     break;
                 case '/':
                     res = num2 / num1;
                     break;
                 default:
                     break;
             }
             return res;
         }
     
     
     }
  • 結果展示

(1) 處理個位數

(2) 處理多位數

5、逆波蘭計算器

  • 需求分析

    30 4 + 5 * 6 -
    
  • 思路分析

    • 例如: (3+4)

      5-6 對應的字尾表示式就是 3 4 + 5 * 6 - , 針對字尾表示式求值步驟如下:

      1.從左至右掃描,將 3 和 4 壓入堆疊;

      2.遇到+運算子,因此彈出 4 和 3(4 為棧頂元素,3 為次頂元素),計算出 3+4 的值,得 7,再將 7 入棧;

      3.將 5 入棧;

      * 5=35,將 35 入棧;

      5.將 6 入棧;

      6.最後是-運算子,計算出 35-6 的值,即 29,由此得出最終結果

  • 程式碼實現

    public class PolandNotation {
        public static void main(String[] args) {
    
           
            
            //先定義一個逆波蘭表示式
            //(30 + 4) * 5 - 6 -> 30 4 + 5 * 6 -
            //為了方便,逆波蘭表示式的數字和符號用空格隔開
            String suffixExpression ="30 4 + 5 * 6 -";
            //思路
            //1.先將suffixExpression放到ArrayList中
            //2.將ArrayList傳遞給一個方法 ,配合棧完成計算
    
    
    
            List<String> rpnList = getListString(suffixExpression);
            System.out.println("rpnList="+rpnList);
    
    
    
            int res = calculate(rpnList);
            System.out.println("計算的結果是:"+res);
    
            
        }
    
       
        //將一個逆波蘭表示式,依次將資料和運算子放入到ArrayList中
        public static List<String> getListString(String suffixExpression){
            //將suffixExpression分割
            String[] split = suffixExpression.split(" ");
            ArrayList<String> list = new ArrayList<>();
            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,由此得出最終結果
        public static int calculate(List<String> ls){
            //建立一個棧,只需要一個棧
            Stack<String> stack = new Stack<>();
            //遍歷 ls
            for (String item:ls) {
                //這裡使用正則表示式來取數
                if(item.matches("\\d+")){//匹配的是多位數
                    //入棧
                    stack.push(item);
                }else {
                    //pop出兩個數並運算,再入棧
                    int num2 = Integer.parseInt(stack.pop());
                    int num1 = Integer.parseInt(stack.pop());
                    int res = 0;
                    if(item.equals("+")){
                        res = num1+num2;
                    }else if(item.equals("-")){
                        res = num1 -num2;//次頂-棧頂
                    }else if(item.equals("*")){
                        res = num1*num2;
                    }else if (item.equals("/")){
                        res = num1/num2;//次頂/棧頂
                    }else {
                        throw new RuntimeException("運算子有誤");
                    }
                    //把res入棧
                    stack.push(""+res);
    
                }
            }
            //最後留在stack中的資料是運算結果
            return Integer.parseInt(stack.pop());
        }
    
    }
  • 結果展示

6、中綴表示式轉換為字尾表示式

通過上面逆波蘭計算器的例子我們發現,字尾表示式適合計算機進行計算,但是我們卻不太容易寫出來,所以接下來我們需要將中綴表示式轉換為字尾表示式

中綴表示式轉換為字尾表示式的一般步驟是

1.初始化兩個棧:運算子棧 s1 和儲存中間結果的棧 s2;

2.從左至右掃描中綴表示式;

3.遇到運算元時,將其壓 s2;

4.遇到運算子時,比較其與 s1 棧頂運算子的優先順序:

1.如果 s1 為空,或棧頂運算子為左括號“(”,則直接將此運算子入棧;

2.否則,若優先順序比棧頂運算子的高,也將運算子壓入 s1;

3.否則,將 s1 棧頂的運算子彈出並壓入到 s2 中,再次轉到(4-1)與 s1 中新的棧頂運算子相比較;

5.遇到括號時:

(1) 如果是左括號“(”,則直接壓入 s1

(2) 如果是右括號“)”,則依次彈出 s1 棧頂的運算子,並壓入 s2,直到遇到左括號為止,此時將這一對括號丟棄

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

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

8.依次彈出 s2 中的元素並輸出,結果的逆序即為中綴表示式對應的字尾表示式

  • 思路分析(重點)

  • 程式碼實現

    public class PolandNotation {
        public static void main(String[] args) {
    
    
    
            //完成將一箇中綴表示式轉成字尾表示式的功能
            //說明
            //1. 1+((2+3)×4)-5 => 轉成  1 2 3 + 4 × + 5 –
            //2. 因為直接對str 進行操作,不方便,因此 先將  "1+((2+3)×4)-5" -> 中綴的表示式對應的List
            //   即 "1+((2+3)×4)-5" => ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
            //3. 將得到的中綴表示式對應的List -> 字尾表示式對應的List
            //   即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]  =》 ArrayList [1,2,3,+,4,*,+,5,–]
            String expression = "1+((2+3)*4)-5";
            List<String> infixExpression = toInfixExpression(expression);
            System.out.println("中綴表示式對應的List="+infixExpression);
    
    
            List<String> strings = parseSuffixExpressionList(infixExpression);
            System.out.println("字尾表示式對應的List="+strings);
    
            System.out.println("expression="+calculate(strings));
    
    
        }
    
        //即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]  =》 ArrayList [1,2,3,+,4,*,+,5,–]
        //方法:將得到的中綴表示式對應的List => 字尾表示式對應的List
        public static List<String> parseSuffixExpressionList(List<String> ls){
            //定義兩個棧
            Stack<String> s1 = new Stack<String>();//符號棧
            //說明:因為s2 這個棧,在整個轉換過程中,沒有pop操作,而且後面我們還需要逆序輸出
            //因此比較麻煩,這裡我們就不用 Stack<String> 直接使用 List<String> s2
            //Stack<String> s2 = new Stack<String>(); // 儲存中間結果的棧s2
            List<String> s2 = new ArrayList<String>(); // 儲存中間結果的Lists2
    
            //遍歷ls
            for (String item:ls ) {
                //如果是一個數就加入到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();//將(彈出s1(消除小括號)
                }else {
                    //當item的優先順序小於等於s1棧頂運算子, 將s1棧頂的運算子彈出並加入到s2中,再次轉到(4.1)與s1中新的棧頂運算子相比較
                    //問題:我們缺少一個比較優先順序高低的方法
                    while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)){
                        s2.add(s1.pop());
                    }
                    //還需要將item壓入棧頂
                    s1.push(item);
                }
            }
            //將s1中剩餘的運算子依次彈出並加入到s2中
            while (s1.size()!=0){
                s2.add(s1.pop());
            }
            //注意因為是存放到List, 因此按順序輸出就是對應的字尾表示式對應的List
            return s2;
        }
    
    
    
    
    
        //將中綴表示式轉成對應的List
        public static  List<String> toInfixExpression(String s){
            //定義一個List,存放中綴表示式對應的內容
            List<String> ls = new ArrayList<>();
            int i = 0;//用於遍歷中綴表示式的字串,指標
            String str;//對多位數的拼接
            char c;//每遍歷到一個字元,就放入到c中
            do {
                //如果c是一個非數字就需要加入到ls中
                if((c = s.charAt(i)) < 48 || (c=s.charAt(i)) >57){
                    ls.add(""+c);
                    i++;
                } else {//如果是一個數,需要考慮多位數的問題
                    str = "";//先將str置成空串
                    while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57){
                        str+=c;//拼接
                        i++;
                    }
                    ls.add(str);
                }
            }while (i < s.length());
            return ls;
        }
    
    
    
    
    
    
    
    
        //將一個逆波蘭表示式,依次將資料和運算子放入到ArrayList中
        public static List<String> getListString(String suffixExpression){
            //將suffixExpression分割
            String[] split = suffixExpression.split(" ");
            ArrayList<String> list = new ArrayList<>();
            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,由此得出最終結果
        public static int calculate(List<String> ls){
            //建立一個棧,只需要一個棧
            Stack<String> stack = new Stack<>();
            //遍歷 ls
            for (String item:ls) {
                //這裡使用正則表示式來取數
                if(item.matches("\\d+")){//匹配的是多位數
                    //入棧
                    stack.push(item);
                }else {
                    //pop出兩個數並運算,再入棧
                    int num2 = Integer.parseInt(stack.pop());
                    int num1 = Integer.parseInt(stack.pop());
                    int res = 0;
                    if(item.equals("+")){
                        res = num1+num2;
                    }else if(item.equals("-")){
                        res = num1 -num2;//次頂-棧頂
                    }else if(item.equals("*")){
                        res = num1*num2;
                    }else if (item.equals("/")){
                        res = num1/num2;//次頂/棧頂
                    }else {
                        throw new RuntimeException("運算子有誤");
                    }
                    //把res入棧
                    stack.push(""+res);
    
                }
            }
            //最後留在stack中的資料是運算結果
            return Integer.parseInt(stack.pop());
        }
    
    }
    
    
    
    
    //編寫一個類Operation,可以返回運算子對應的優先順序
    class Operation {
        private static int ADD = 1;
        private static int SUB = 1;
        private static int MUL = 2;
        private static int DIV = 2;
    
        //寫一個方法返回對應的優先順序數字
        public static int getValue(String operation){
            int result = 0;
            switch (operation){
                case "+":
                    result = ADD;
                    break;
                case "-":
                    result = SUB;
                    break;
                case "*":
                    result = MUL;
                    break;
                case "/":
                    result = DIV;
                    break;
                default:
                    System.out.println("不存在該運算子:"+operation);
                    break;
            }
            return result;
        }
    }
  • 結果展示

7、完整版逆波蘭計算器

  • 功能描述

    1. 支援 + - * / ( )
    2. 多位數,支援小數,
    3. 相容處理, 過濾任何空白字元,包括空格、製表符、換頁符
  • 程式碼實現

    package com.qjd.stack;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Stack;
    import java.util.regex.Pattern;
    
    public class ReversePolishMultiCalc {
        /**
         * 匹配 + - * / ( ) 運算子
         */
        static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)";
        static final String LEFT = "(";
        static final String RIGHT = ")";
        static final String ADD ="+";
        static final String MINUS = "-";
        static final String TIMES = "*";
        static final String DIVISION = "/";
        /**
         * 加減 + -
         */
        static final int LEVEL_01 = 1;
    
        /**
         * 乘除 * /
         */
        static final int LEVEL_02 = 2;
        /**
         * 括號
         */
        static final int LEVEL_HIGH = Integer.MAX_VALUE;
        static Stack<String> stack = new Stack<>();
        static List<String> data = Collections.synchronizedList(new ArrayList<String>());
    
        /**
         * 去除所有空白符
         *
         * @param s
         * @return
         */
        public static String replaceAllBlank(String s) {
    // \\s+ 匹配任何空白字元,包括空格、製表符、換頁符等等, 等價於[ \f\n\r\t\v]
            return s.replaceAll("\\s+", "");
        }
    
        /**
         * 判斷是不是數字 int double long float
         * 尚矽谷 Java 資料結構和演算法
         * 更多 Java –大資料 –前端 –python 人工智慧 -區塊鏈資料下載,可訪問百度:尚矽谷官網
         * 第 94頁
         *
         * @param s
         * @return
         */
        public static boolean isNumber(String s) {
            Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$");
            return pattern.matcher(s).matches();
        }
    
        /**
         * 判斷是不是運算子
         *
         * @param s
         * @return
         */
        public static boolean isSymbol(String s) {
            return s.matches(SYMBOL);
        }
    
        /**
         * 匹配運算等級
         *
         * @param s
         * @return
         */
        public static int calcLevel(String s) {
            if ("+".equals(s) || "-".equals(s)) {
                return LEVEL_01;
            } else if ("*".equals(s) || "/".equals(s)) {
    
                return LEVEL_02;
            }
            return LEVEL_HIGH;
        }
    
        /**
         * 匹配
         *
         * @param s
         * @throws Exception
         */
        public static List<String> doMatch(String s) throws Exception {
            if (s == null || "".equals(s.trim())) throw new RuntimeException("data is empty");
            if (!isNumber(s.charAt(0) + "")) throw new RuntimeException("data illeagle,start not with a number");
            s = replaceAllBlank(s);
            String each;
            int start = 0;
            for (int i = 0; i < s.length(); i++) {
                if (isSymbol(s.charAt(i) + "")) {
                    each = s.charAt(i) + "";
    //棧為空,(操作符,或者 操作符優先順序大於棧頂優先順序 && 操作符優先順序不是( )的優先順序 及是 )不能直接入棧
                    if (stack.isEmpty() || LEFT.equals(each)
                            || ((calcLevel(each) > calcLevel(stack.peek())) && calcLevel(each) < LEVEL_HIGH)) {
    
                        stack.push(each);
                    } else if (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())) {
    //棧非空,操作符優先順序小於等於棧頂優先順序時出棧入列,直到棧為空,或者遇到了(,最後操作符入棧
                        while (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())) {
                            if (calcLevel(stack.peek()) == LEVEL_HIGH) {
                                break;
                            }
                            data.add(stack.pop());
                        }
                        stack.push(each);
                    } else if (RIGHT.equals(each)) {
    // ) 操作符,依次出棧入列直到空棧或者遇到了第一個)操作符,此時)出棧
                        while (!stack.isEmpty() && LEVEL_HIGH >= calcLevel(stack.peek())) {
                            if (LEVEL_HIGH == calcLevel(stack.peek())) {
                                stack.pop();
                                break;
                            }
                            data.add(stack.pop());
                        }
                    }
                    start = i; //前一個運算子的位置
                } else if (i == s.length() - 1 || isSymbol(s.charAt(i + 1) + "")) {
                    each = start == 0 ? s.substring(start, i + 1) : s.substring(start + 1, i + 1);
                    if (isNumber(each)) {
                        data.add(each);
    
                        continue;
                    }
                    throw new RuntimeException("data not match number");
                }
            }
    //如果棧裡還有元素,此時元素需要依次出棧入列,可以想象棧裡剩下棧頂為/,棧底為+,應該依次出棧入列,可以直接翻轉整個 stack 新增到佇列
            Collections.reverse(stack);
            data.addAll(new ArrayList < > (stack));
            System.out.println(data);
            return data;
        }
    
        /**
         * 算出結果
         *
         * @param list
         * @return
         */
        public static Double doCalc(List<String> list) {
            Double d = 0d;
            if (list == null || list.isEmpty()) {
                return null;
            }
            if (list.size() == 1) {
                System.out.println(list);
    
                d = Double.valueOf(list.get(0));
                return d;
            }
            ArrayList<String> list1 = new ArrayList <>();
            for (int i = 0; i < list.size(); i++) {
                list1.add(list.get(i));
                if (isSymbol(list.get(i))) {
                    Double d1 = doTheMath(list.get(i - 2), list.get(i - 1), list.get(i));
                    list1.remove(i);
                    list1.remove(i - 1);
                    list1.set(i - 2, d1 + "");
                    list1.addAll(list.subList(i + 1, list.size()));
                    break;
                }
            }
            doCalc(list1);
            return d;
        }
    
        /**
         * 運算
         *
         * @param s1
         * @param s2
         * @param symbol
         * @return
         */
    
        public static Double doTheMath(String s1, String s2, String symbol) {
            Double result;
            switch (symbol) {
                case ADD:
                result = Double.valueOf(s1) + Double.valueOf(s2);
                break;
                case MINUS:
                    result = Double.valueOf(s1) - Double.valueOf(s2);
                    break;
                case TIMES:
                    result = Double.valueOf(s1) * Double.valueOf(s2);
                    break;
                case DIVISION:
                    result = Double.valueOf(s1) / Double.valueOf(s2);
                    break;
                default:
                    result = null;
            }
            return result;
        }
    
        public static void main(String[] args) {
    //String math = "9+(3-1)*3+10/2";
            String math = "12.8 + (2 - 3.55)*4+10/5.0";
            try {
                doCalc(doMatch(math));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
  • 結果展示

到這裡關於棧的資料結構與演算法就結束啦ヽ(✿゚▽゚)ノ,我認為資料結構與演算法的內容還是枯燥而且有難度的,但是它的重要性不言而喻,所以大家一定要堅持下去,歡迎大家提出問題,我們一起進步!!!