Java版-資料結構-棧

小白程式之路發表於2019-03-12

介紹

棧是一種後進先出的線性表資料結構,分為棧頂和棧底兩端,僅允許在表的一端插入元素,這一端被稱為棧頂,另外一端稱之為棧底。棧,只有兩種操作,分為入棧(壓棧)和出棧(退棧);向棧中新增元素的操作叫做入棧,相反從棧中刪除元素叫做出棧。

特點

  • 只能從棧頂新增元素或者刪除元素
  • 後進先出的資料結構,Last In First Out(LIFO)

為了大家更好的形象瞭解我們通過示意圖來看一下棧的入棧出棧操作

入棧操作示意圖

image-20190311222134128

出棧操作示意圖(後進的元素先出)

image-20190311222101553

棧的基本操作

  • 向棧中新增一個元素(入棧)
void push(E e)
複製程式碼
  • 從棧中刪除一個元素(出棧)
E pop()
複製程式碼
  • 檢視棧頂元素
E peek()
複製程式碼
  • 檢視棧中元素個數
int getSize()
複製程式碼
  • 判斷棧是否為空
boolean isEmpty()
複製程式碼

實現棧的方式,實際上底層有多種實現方式,比如:動態陣列等,這裡我們使用Java語言本身為我們提供的集合LinkedList

介面定義:Stack
public interface Stack<E> {

    /**
     * 向棧中新增元素
     *
     * @param e
     */
    void push(E e);

    /**
     * 從棧中刪除元素
     */
    void pop();

    /**
     * 獲取棧頂元素
     *
     * @return
     */
    E peek();

    /**
     * 獲取棧中元素個數
     *
     * @return
     */
    int getSize();

    /**
     * 判斷棧中是否為空
     *
     * @return
     */
    boolean isEmpty();

}
複製程式碼
LinkedListStack 類實現介面Stack
public class LinkedListStack<E> implements Stack<E> {
    /**
     * 存放棧元素
     */
    LinkedList<E> list;

    /**
     * 構造棧結構
     */
    public LinkedListStack() {
        list = new LinkedList<>();
    }

    @Override
    public void push(E e) {
        list.addLast(e);
    }

    @Override
    public void pop() {
        list.removeLast();
    }

    @Override
    public E peek() {
        return list.getLast();
    }

    @Override
    public int getSize() {
        return list.size();
    }

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public String toString() {
        return "LinkedListStack{" +
                "list=" + list +
                '}';
    }
}
複製程式碼
測試類:LinkedListStackTest
@Test
public void testLinkedListStack() {
    // 棧
    Stack<String> stack = new LinkedListStack<>();
    // 準備入棧元素
    List<String> prepareElements = Arrays.asList("A", "B", "C", "D", "E");
    // 入棧
    prepareElements.forEach(x -> {
        stack.push(x);
        System.out.println("入棧操作:" + stack);
    });
    // 出棧
    stack.pop();
    System.out.println("出棧操作:" + stack);
    // 獲取棧頂元素
    String peekElement = stack.peek();
    System.out.println("棧頂元素:" + peekElement);
    // 獲取棧中元素的個數
    int stackSize = stack.getSize();
    System.out.println("棧中元素個數:" + stackSize);
}
複製程式碼
執行結果
入棧操作:LinkedListStack{list=[A]}
入棧操作:LinkedListStack{list=[A, B]}
入棧操作:LinkedListStack{list=[A, B, C]}
入棧操作:LinkedListStack{list=[A, B, C, D]}
入棧操作:LinkedListStack{list=[A, B, C, D, E]}
出棧操作:LinkedListStack{list=[A, B, C, D]}
棧頂元素:D
棧中元素個數:4
複製程式碼

棧的應用

虛擬機器棧的入棧和出棧操作

在Java虛擬機器執行時資料區有一塊被稱之為:虛擬機器棧,它是執行緒私有的,宣告週期與執行緒相同。

我們編寫的每個Java方法,每個方法都會在執行的時候同時都會建立一個棧幀(Stack Frame)用於儲存區域性變數表、運算元棧、動態連結、方法出口等資訊。每一個方法從呼叫直至執行完成的過程,就對應這一個棧幀在虛擬機器棧中入棧出棧的過程。

現在我們假設有A、B、C三個方法,在A方法中呼叫B方法(A->B),在B方法中呼叫C方法(B->C),C方法執行本方法業務邏輯。

image-20190311230707333

當程式執行到A()方法的中的第二行時,此時程式會中斷A()方法並開始呼叫B()方法,然後會在虛擬機器棧中記錄呼叫B()方法的棧幀,我這裡暫且稱之為A2(實際儲存的並不是O(∩_∩)O哈!)示意圖如下:

image-20190311231917661

同理,當程式執行到B()方法中第二行時,此時程式也會中斷B()方法開始呼叫C()方法,然後同樣地會在虛擬機器棧中生成呼叫C()方法的棧幀並記錄,我這裡暫且稱之為B2,示意圖如下:

image-20190311232716815

當程式開始執行到C()方法時,直到執行完C()方法時,這時候,程式該如何執行呢?

image-20190311233500400

此時就要檢視一下虛擬機器棧了,發現虛擬機器棧,棧中棧頂的元素是B2,我們的程式就知道了,它是執行到B()方法的B2位置就中斷了,去執行C()方法了;現在C()方法執行完成之後,它就可以跳回到B2的位置繼續執行了,當B()方法執行完之後,虛擬機器棧中的B2棧幀也就可以出棧了,依次類推....

image-20190311234438000

如果一個方法,使用遞迴呼叫,若遞迴臨界點判斷有誤,則方法就會一直的被進行入棧操作,如果超過虛擬機器棧的預設容量大小,則會出現我們常見的 StackOverflowError 異常

完整版程式碼GitHub倉庫地址:Java版資料結構-棧 歡迎大家【關注】和【Star

本次我們完成的是基於Java自身自帶的集合LinkedList來實現棧,有興趣的童鞋,可以使用動態陣列方式來實現;接下來,筆者還會一一的實現其它常見的陣列結構。

  • 靜態陣列
  • 動態陣列
  • 佇列
  • 連結串列
  • 迴圈連結串列
  • 二分搜尋樹
  • 優先佇列
  • 線段樹
  • 字典樹
  • AVL
  • 紅黑樹
  • 雜湊表
  • ....

持續更新中,歡迎大家關注公眾號:小白程式之路(whiteontheroad),第一時間獲取最新資訊!!!

Java版-資料結構-棧

部落格地址-->

相關文章