圖解java數據結構之棧(Stack),你肯定不看看嗎?

前言

以前我八種數據結構大概的輪廓歸攏,可是因爲是總體的歸攏,內容不夠詳細,所以特寫此篇文章針對數據結構中的棧進行更詳細的瞭解,望對各位朋友有所幫助!若有錯誤,煩請私信或留言告知,在下定當及時更正,以避免誤人!java

一.棧(Stack)的介紹

棧是一個先入後出(FILO:First In Last Out)的有序列表。

棧(Stack)是限制線性表中元素的插入和刪除只能在同一端進行的一種特殊線性表。

容許插入和刪除的一端,爲變化的一端,稱爲棧頂(Top),另外一端爲固定的一端,稱爲
棧底(Bottom)。

根據棧的定義可知,最早放入棧中元素在棧底,最後放入的元素在棧頂

而刪除元素恰好相反,最後放入的元素最早刪除,最早放入的元素最後刪除

2、入棧(壓棧)圖解

圖解java數據結構之棧(Stack),你肯定不看看嗎?

3、出棧(彈棧)圖解

圖解java數據結構之棧(Stack),你肯定不看看嗎?

4、應用場景

1)子程序的調用:在跳往子程序前,會先將下個指令的地址存到堆棧中,直到子程序執行完後再將地址取出,以回到原來的程序中。程序員

2)處理遞歸調用:和子程序的調用相似,只是除了儲存下一個指令的地址外,也將參數、區域變量等數據存入堆棧中。正則表達式

3)表達式的轉換[中綴表達式轉後綴表達式]與求值(實際解決)。express

4)二叉樹的遍歷。數組

5)圖形的深度優先(depth-first)搜索法。數據結構

5、用數組模擬棧

圖解java數據結構之棧(Stack),你肯定不看看嗎?

思路:

1)定義一個top來表示棧頂,初始化爲-1ide

2)入棧的操做,當有數據加入到棧時,top++; stack[top] = data;測試

3)出棧的操做,int value = stack[top]; top--; return value;this

代碼實現:

//定義一個 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;
    }
    //棧空
    public boolean isEmpty() {
        return top == -1;
    }
    //入棧-push
    public void push(int value) {
        //先判斷棧是否滿
        if(isFull()) {
            System.out.println("棧滿");
            return;
        }
        top++;
        stack[top] = value;
    }
    //出棧-pop, 將棧頂的數據返回
    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]);
        }
    }
}

6、棧實現中綴表達式計算器

中綴表達式就是常見的運算表達式,如(3+4)×5-6
圖解java數據結構之棧(Stack),你肯定不看看嗎?指針

public class Calculator {

    public static void main(String[] args) {
        //根據前面老師思路,完成表達式的運算
        String expression = "7*2*2-5+1-5+3-4"; // 15//如何處理多位數的問題?
        //建立兩個棧,數棧,一個符號棧
        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); // 1 + 3
                }
            } else { //若是是數,則直接入數棧

                //numStack.push(ch - 48); //? "1+3" '1' => 1
                //分析思路
                //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))) {
                        //若是後一位是運算符,則入棧 keepNum = "1" 或者 "123"
                        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);
    }

}

//先建立一個棧,直接使用前面建立好
//定義一個 ArrayStack2 表示棧, 須要擴展功能
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];
    }

    //增長一個方法,能夠返回當前棧頂的值, 可是不是真正的pop
    public int peek() {
        return stack[top];
    }

    //棧滿
    public boolean isFull() {
        return top == maxSize - 1;
    }
    //棧空
    public boolean isEmpty() {
        return top == -1;
    }
    //入棧-push
    public void push(int value) {
        //先判斷棧是否滿
        if(isFull()) {
            System.out.println("棧滿");
            return;
        }
        top++;
        stack[top] = value;
    }
    //出棧-pop, 將棧頂的數據返回
    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; // res 用於存放計算的結果
        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;
    }
}

7、棧實現後綴表達式(逆波蘭)計算器

中綴表達式的求值是咱們人最熟悉的,可是對計算機來講卻很差操做,所以,在計算結果時,每每會將中綴表達式轉成其它表達式來操做(通常轉成後綴表達式)

後綴表達式又稱逆波蘭表達式,與前綴表達式類似,只是運算符位於操做數以後,舉例說明: (3+4)×5-6 對應的後綴表達式就是 3 4 + 5 × 6 –

再好比:
圖解java數據結構之棧(Stack),你肯定不看看嗎?
1)後綴表達式的計算機求值

從左至右掃描表達式,遇到數字時,將數字壓入堆棧,遇到運算符時,彈出棧頂的兩個數,用運算符對它們作相應的計算(次頂元素 和 棧頂元素),並將結果入棧;重複上述過程直到表達式最右端,最後運算得出的值即爲表達式的結果

例如: (3+4)×5-6 對應的後綴表達式就是 3 4 + 5 × 6 - , 針對後綴表達式求值步驟以下:

(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,由此得出最終結果

代碼實現

//完成對逆波蘭表達式的運算
/*
 * 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<String>();
    // 遍歷 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());
}

2)中綴表達式轉後綴表達式

具體步驟以下:
(1) 初始化兩個棧:運算符棧s1和儲存中間結果的棧s2;
(2) 從左至右掃描中綴表達式;
(3) 遇到操做數時,將其壓s2;
(4) 遇到運算符時,比較其與s1棧頂運算符的優先級:
(4-1) 若是s1爲空,或棧頂運算符爲左括號「(」,則直接將此運算符入棧;
(4-2) 不然,若優先級比棧頂運算符的高,也將運算符壓入s1;
(4-3) 不然,將s1棧頂的運算符彈出並壓入到s2中,再次轉到(4-1)與s1中新的棧頂運算符相比較;
(5) 遇到括號時:
(5-1) 若是是左括號「(」,則直接壓入s1
(5-2) 若是是右括號「)」,則依次彈出s1棧頂的運算符,並壓入s2,直到遇到左括號爲止,此時將這一對括號丟棄
(6)重複步驟2至5,直到表達式的最右邊
(7)將s1中剩餘的運算符依次彈出並壓入s2
(8)依次彈出s2中的元素並輸出,結果的逆序即爲中綴表達式對應的後綴表達式
舉例說明:將中綴表達式「1+((2+3)×4)-5」轉換爲後綴表達式的過程以下:
圖解java數據結構之棧(Stack),你肯定不看看嗎?
代碼實現:

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

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> infixExpressionList = toInfixExpressionList(expression);
        System.out.println("中綴表達式對應的List=" + infixExpressionList); // ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
        List<String> suffixExpreesionList = parseSuffixExpreesionList(infixExpressionList);
        System.out.println("後綴表達式對應的List" + suffixExpreesionList); //ArrayList [1,2,3,+,4,*,+,5,–] 

        System.out.printf("expression=%d", calculate(suffixExpreesionList)); // ?

        /*

        //先定義給逆波蘭表達式
        //(30+4)×5-6  => 30 4 + 5 × 6 - => 164
        // 4 * 5 - 8 + 60 + 8 / 2 => 4 5 * 8 - 60 + 8 2 / + 
        //測試 
        //說明爲了方便,逆波蘭表達式 的數字和符號使用空格隔開
        //String suffixExpression = "30 4 + 5 * 6 -";
        String suffixExpression = "4 5 * 8 - 60 + 8 2 / +"; // 76
        //思路
        //1. 先將 "3 4 + 5 × 6 - " => 放到ArrayList中
        //2. 將 ArrayList 傳遞給一個方法,遍歷 ArrayList 配合棧 完成計算

        List<String> list = getListString(suffixExpression);
        System.out.println("rpnList=" + list);
        int res = calculate(list);
        System.out.println("計算的結果是=" + res);

        */
    }

    //即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]  =》 ArrayList [1,2,3,+,4,*,+,5,–]
    //方法:將獲得的中綴表達式對應的List => 後綴表達式對應的List
    public static List<String> parseSuffixExpreesionList(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());
        }

        return s2; //注意由於是存放到List, 所以按順序輸出就是對應的後綴表達式對應的List

    }

    //方法:將 中綴表達式轉成對應的List
    //  s="1+((2+3)×4)-5";
    public static List<String> toInfixExpressionList(String s) {
        //定義一個List,存放中綴表達式 對應的內容
        List<String> ls = new ArrayList<String>();
        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++; //i須要後移
            } else { //若是是一個數,須要考慮多位數
                str = ""; //先將str 置成"" '0'[48]->'9'[57]
                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(" ");
        List<String> list = new ArrayList<String>();
        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<String>();
        // 遍歷 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;
    }
}

結尾

本篇章主要是針對棧結構的具體解析,但願對你們有所幫助

相關文章
相關標籤/搜索