1. 程式人生 > 實用技巧 >資料結構與演算法:棧

資料結構與演算法:棧

什麼是棧?

棧(stack)又名堆疊,它是一種運算受限的線性表。限定僅在表尾進行插入和刪除操作的線性表。這一端被稱為棧頂,相對地,把另一端稱為棧底。向一個棧插入新元素又稱作進棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成為新的棧頂元素;從一個棧刪除元素又稱作出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成為新的棧頂元素。

使用陣列模擬棧

public class ArrayStackDemo {
​
    public static void main(String[] args) { //測試陣列模擬棧
        ArrayStack arrayStack = new ArrayStack(5);
        
boolean loop = true; String key = ""; Scanner scanner = new Scanner(System.in); while (loop){ System.out.println("list:遍歷棧內資料"); System.out.println("exit:退出程式"); System.out.println("push:向棧內推入資料"); System.out.println("pop:從棧內取出資料"); ​ key
= scanner.next(); switch (key){ case "list": arrayStack.list(); break; case "push": int value = scanner.nextInt(); arrayStack.push(value); break;
case "pop": try { int result = arrayStack.pop(); System.out.printf("取出的資料為:%d\n", result); }catch (Exception e){ System.out.println(e.getMessage()); } break; case "exit": scanner.close(); loop = false; break; default: break; } } ​ System.out.println("程式已退出!"); } } ​ //陣列模擬棧的類 class ArrayStack{ private int maxSize; //棧的最大容量 private int[] arrayStack; //陣列模擬棧 private int top = -1; //模擬棧的頂端指標,指向棧的頂端,預設為-1,即棧空 public ArrayStack(int maxSize) { this.maxSize = maxSize; arrayStack = new int[maxSize]; } ​ /** * 因為棧是從索引為0的底端開始壓入資料的,所以 * 當頂端指標為maxSize-1時,棧為滿 * 當頂端指標為-1時,棧為空 */ public boolean isFull(){ //判斷是否為滿 return top == maxSize-1; } public boolean isEmpty(){ //判斷是否為空 return top == -1; } ​ /** * 思路: * 1. 頂端指標top向後移一位 * 2. 將資料存放在指標指向的位置 * @param data */ public void push(int data){ //向棧內壓入資料 if (isFull()){ System.out.println("棧滿!"); return; } top++; arrayStack[top] = data; } ​ /** * 思路: * 1. 將頂端指標top指向的位置的資料彈出 * 2. 將頂端指標top向前移一位 * @return */ public int pop(){ if (isEmpty()){ throw new RuntimeException("棧為空!"); } int result = arrayStack[top]; top--; return result; } ​ public void list(){ //遍歷陣列模擬棧 if (isEmpty()){ System.out.println("棧為空!"); return; } for (int i=top; i>=0; i--){ System.out.printf("arrayStack[%d]=%d\n", i, arrayStack[i]); } } }

棧的應用——表示式求值

在我們日常中使用的表示式,如:(30+2)*5-16/4,稱做中綴表示式。它對於我們來說是易理解的,但對於計算機來說卻是非常複雜的,所以我們需要把表示式換做在計算器看來簡單的易懂的字尾表示式(逆波蘭表示式),如:30 2 + 5 * 16 4 / -。而棧就能幫我們把中綴表示式轉換成字尾表示式。

中綴表示式轉字尾的邏輯

是以從左到右,按運算先後的順序來轉換的,運算子則放在運算數的後面,如 a+b-c 就是 a b + c -

(30+2)*5-16/4為例,流程為

  1. 30+2 轉換為 30 2 +

  2. (30+2)*5 轉換為 30 2 + 5 *

  3. 16/4 轉換為 16 4 /

  4. (30+2)*5-16/4 轉換為 30 2 + 5 * 16 4 / -

程式碼實現

public class RPNCalculator {
    
    public static void main(String[] args) {
        String expression = "(30+2)*5-16/4"; //對應的字尾表示式為"30 2 + 5 * 16 4 / -"
        
        List<String> expressionList = splitToArrayList(expression);
        System.out.println(expressionList);//輸出原先的中綴表示式
        
        List<String> suffixExpression = toSuffixExpression(expressionList);
        System.out.println(suffixExpression);//輸出轉換後的字尾表示式
        
        System.out.println(expression+"="+calculator(suffixExpression));//輸出計算後的最終結果
    }
    
    /**
     * 將中綴表示式轉為字尾表示式
     * 邏輯思路:
     *  遍歷中綴表示式集合:
     *  1.如果是數字,直接存進字尾表示式集合中
     *  2.如果是括號"()":
     *    1)如果是"(", 直接壓入操作符棧中
     *    2)如果是")", 則遍歷操作符棧,檢視操作符棧的頂端是否是"(",
     *       如果不是則把頂端彈出新增到字尾表示式集合中,是則直接把"("從棧彈出即可
     *  3.如果是其他操作符:
     *    1)如果操作符棧為空,則直接壓入棧中
     *    2)如果不為空,則和棧頂操作符比較優先順序,如果棧頂操作符優先順序>=傳入的操作符優先順序,
     *       則先將棧頂操作符彈出並新增到字尾表示式集合中,再把傳入操作符壓入棧中
     *       不是則直接壓入棧中
     *   遍歷完中綴表示式集合後,再把操作符棧中剩餘的操作符彈出並新增到字尾表示式集合中
     * @param expression 中綴表示式集合
     * @return
     */
    public static List<String> toSuffixExpression(List<String> expression){
        Stack<String> operator = new Stack<>();//操作符棧,用於儲存操作符
        List<String> suffixExpression = new ArrayList<>();//字尾表示式陣列集合
        for (String item : expression){//遍歷中綴表示式中的每個資料
            if (item.matches("^-?\\d+$")){//使用正則表示式判斷該字串是否是整數
                suffixExpression.add(item);//如果是整數則直接加集合中
            }else if (item.equals("(")){
                operator.push(item);//如果是(,則壓入操作符棧中
            }else if (item.equals(")")){
                while( !operator.peek().equals("(") ){//判斷棧頂是否是"("
                    suffixExpression.add(operator.pop());//如果不是則彈出棧頂並新增到字尾表示式集合中
                }
                operator.pop();//最後在把棧底的"("彈出
            }else {//非括號的其他操作符
                //棧為空 或 優先順序>=棧頂的操作符 則將棧頂操作符彈出並新增到字尾表示式集合中
                while (!operator.isEmpty() && priority(item) <= priority(operator.peek())){
                    suffixExpression.add(operator.pop());
                }
                operator.push(item);//最後再把操作符壓入棧中
            }
        }
​
        while ( !operator.isEmpty() ){//把操作符棧中剩下的操作符彈出並新增到字尾表示式集合中
            suffixExpression.add(operator.pop());
        }
​
        return suffixExpression;
    }
​
    //計算方法
    public static int calculator(List<String> suffixExpression){
        Stack<Integer> numStack = new Stack<>();//用於儲存表示式中每段計算的結果
        int result = 0;
        for (String item : suffixExpression){//遍歷字尾表示式集合
            if (item.matches("^-?\\d+$")){//判斷是否是整數
                numStack.push(Integer.parseInt(item));//如果是則直接壓入棧中
            }else {
                //如果是操作符,則從棧中彈出倆個數字並計算結果
                int num1 = numStack.pop();
                int num2 = numStack.pop();
                switch (item){
                    case "+":
                        result = num1 + num2;
                        break;
                    case "-":
                        result = num2 - num1;
                        break;
                    case "*":
                        result = num1 * num2;
                        break;
                    case "/":
                        result = num2 / num1;
                        break;
                    default:
                        throw new RuntimeException("表示式含有未能識別的符號!");
                }
                numStack.push(result);//再把計算後的結果壓入棧中
            }
        }
        //最後棧中剩餘的唯一資料就是表示式最後的計算結果
        return numStack.pop();
    }
    
     //將表示式的字串分割成陣列集合
    public static List<String> splitToArrayList(String expression){
        List<String> expressionList = new ArrayList<>();
        int index = 0;//字串下標,用於字串內的字元的鎖定
        char c = ' ';
        String s = "";//用於拼接數字
        while (index < expression.length()){//判斷下標是否已超出字串長度
            c = expression.charAt(index);//根據下標從字串取出相應的字元
            if (c > 47 && c < 58){//根據ascii碼判斷字元是否是數字
                s += c;//把數字新增到拼接字串
                while (index<expression.length()-1 && expression.charAt(index+1)>47 && expression.charAt(index+1)<58){//判斷是否是多位數
                    //如果是多位數,將下個下標的字元也新增進拼接字串
                    s += expression.charAt(index+1);
                    index++;
                }
                expressionList.add(s);
                s = "";//將拼接字串清空
            }else {//如果為其他字元則直接新增進集合中
                expressionList.add(c+"");
            }
            index ++;
        }
        return expressionList;
    }
    
    //返回各個操作符的優先順序
    public static int priority(String s){
        switch (s){
            case "+":
            case "-":
                return 1;
            case "*":
            case "/":
                return 2;
            case "(":
            case ")":
                return 0;
            default:
                throw new RuntimeException("表示式含有未能識別的符號!");
        }
    }
}



翻譯 朗讀 複製 正在查詢,請稍候…… 重試 朗讀 複製 複製 朗讀 複製 via 谷歌翻譯(國內)