圖解數據結構:棧和隊列

前言

閱讀此篇之前,強烈建議先仔細閱讀上一篇 圖解數據結構:數組和單鏈表 ,會有事半功倍的效果,並且此篇的代碼,基本上是複用上一篇的實現。

上一篇主要講解了數組和鏈表這兩種線性結構的特點、區別、時間複雜度分析等。對數組和鏈表的劃分,實際上是物理結構(存儲結構)的劃分。
物理結構有兩種基本的結構:順序存儲結構、鏈式存儲結構。而本篇所講解的棧和隊列屬於邏輯結構上的劃分。邏輯結構分爲線性結構、非線性結構。

  • 線性結構:有且僅有一個開始節點和一個終端節點,每個節點最多隻有一個直接前驅和一個直接後繼。代表結構:棧、隊列
  • 非線性結構:一個節點可能有多個直接前驅和多個直接後繼。代表結構:樹、圖

本篇主要講解棧和隊列的特點、區別,以及用數組和鏈表分別實現棧和隊列。

堆棧(英語:stack)又稱爲棧或堆疊,是計算機科學中的一種抽象數據類型,只允許在有序的線性數據集合的一端(稱爲堆棧頂端,英語:top)進行加入數據(英語:push)和移除數據(英語:pop)的運算。因而按照後進先出(LIFO, Last In First Out)的原理運作。

棧的主要特點就是LIFO(Last In First Out,後進先出),並且程序只能操作棧的一端,被操作的一端叫做棧頂(Top)。所以棧的使用非常簡單,但是實現的功能卻非常強大。
棧的主要操作有兩個個:入棧(push)、出棧(pop)。

入棧(push)

入棧操作
如圖所示,棧就像一個瓶子,只有一個口。三個元素A、B、C先後入棧,先入棧的放在底部,後入棧的放在上面。

出棧(pop)

出棧操作
根據圖示,棧頂的元素最先出棧。這與入棧的順序剛好相反,入棧順序是A->B->C,出棧順序是C->B->A。也就是說:棧是LIFO(Last In First Out,後進先出的)。
看似簡單的棧,應用十分廣泛。操作系統的函數調用、各類編輯器的撤銷操作的實現都離不開棧。

棧有兩種實現方式:順序棧鏈式棧

順序棧

順序棧用數組實現,基於上一篇 圖解數據結構:數組和單鏈表 ,我們實現了動態數組,實際上用數組實現棧,就是將數組的增、刪操作限制在頭部或者尾部,即只能在數組的一端操作元素,就成了順序棧,複用上一篇的代碼,實現順序棧就很簡單了。

// 動態數組實現順序棧
public class ArrayStack<E> {

	// 此處ArrayList爲上一篇博客所實現的
    private ArrayList<E> list;

    public ArrayStack() {
        list = new ArrayList();
    }

    /**
     * 入棧
     * @param e
     * @return
     */
    public E push(E e) {
        list.add(e);
        return e;
    }

    /**
     * 出棧
     * @return
     */
    public E pop() {
        return list.remove();
    }

    /**
     * 查看棧頂元素
     * @return
     */
    public E peek() {
        return list.get(list.size() - 1);
    }
}

注意:pop()peek()方法都能返回棧頂元素,pop()方法會刪除棧頂元素,也就是出棧。而peek()方法僅僅是查看棧頂元素,不會刪除棧頂元素。
以上幾個方法的時間複雜度在動態數組ArrayList中都已經分析過了,此處不再贅述。
完整代碼下載地址:
Github:ArrayStack.java

鏈式棧

鏈式棧是用鏈表實現棧,也就是上一篇實現的LinkedList。由於複用了上一篇的代碼,所以實現起來也非常簡單,基本上只需要把順序棧中ArrayList換成LinkedList就可以了。

隊列

隊列,又稱爲佇列(queue),是先進先出(FIFO, First-In-First-Out)的線性表。在具體應用中通常用鏈表或者數組來實現。隊列只允許在後端(稱爲rear)進行插入操作,在前端(稱爲front)進行刪除操作。
隊列的操作方式和堆棧類似,唯一的區別在於隊列只允許新數據在後端進行添加。

與棧(stack)不同的是,隊列是FIFO(First In First Out,先進先出),進入隊列的一端叫尾部(rear),出隊列的一端叫頭部(front)。隊列的主要操作也有兩個:入隊(offer)、出隊(poll)

入隊

入隊操作
從圖中可以看到,A、B、C三個元素都是從隊尾(rear)進入,就像現實生活中的排隊,先來的就排在前面。

出隊

出隊操作
從圖中可以看出,出隊的順序是A->B->C,也就是入隊的順序,即說明了隊列是遵循FIFO的。隊列的引用也十分廣泛,鎖的實現、生產者-消費者模型等都離不開隊列。

隊列也有兩種實現方式:順序隊列鏈式隊列

順序隊列

順序隊列用數組實現,基於上一篇 圖解數據結構:數組和單鏈表 ,我們實現了動態數組,用數組實現隊列,就是將數組的增操作限制在尾部,刪操作限制在頭部,即分別只能在數組的一端操作元素,就成了順序隊列,複用上一篇的代碼。

public class ArrayQueue<E> {

	// 此處ArrayList爲上一篇博客所實現的
    private ArrayList<E> list;

    public ArrayQueue() {
        list = new ArrayList();
    }

    /**
     * 出隊
     * @param e
     */
    public void offer(E e) {
        list.add(e);
    }

    /**
     * 入隊
     * @return
     */
    public E poll() {
        return list.remove(0);
    }

    /**
     * 查看隊列頭部元素
     * @return
     */
    public E peek() {
        return list.get(0);
    }

}

注意:poll()peek()方法都能返回隊列頭部元素,poll()方法會刪除隊列頭部元素,也就是出隊。而peek()方法僅僅是查看隊列頭部元素,不會刪除隊列頭部元素。
完整代碼下載地址:
Github:ArrayQueue.java

鏈式隊列

鏈式隊列是用鏈表實現隊列,也就是上一篇實現的LinkedList。由於複用了上一篇的代碼,所以實現起來也非常簡單,基本上只需要把順序隊列中ArrayList換成LinkedList就可以了。

總結

對於棧和隊列的簡單實現,其實上一篇就已經實現過了,所以本篇的重點是理解棧和隊列的工作方式、結構區別、使用區別等。棧和隊列是比較簡單的線性結構,但是簡單不代表用得少。實際上棧和隊列的應用非常廣泛,理解其工作原理,是使用好棧和隊列的第一步,也是最重要的一步。

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