前言
閱讀此篇之前,強烈建議先仔細閱讀上一篇 圖解數據結構:數組和單鏈表 ,會有事半功倍的效果,並且此篇的代碼,基本上是複用上一篇的實現。
上一篇主要講解了數組和鏈表這兩種線性結構的特點、區別、時間複雜度分析等。對數組和鏈表的劃分,實際上是物理結構(存儲結構)的劃分。
物理結構有兩種基本的結構:順序存儲結構、鏈式存儲結構。而本篇所講解的棧和隊列屬於邏輯結構上的劃分。邏輯結構分爲線性結構、非線性結構。
- 線性結構:有且僅有一個開始節點和一個終端節點,每個節點最多隻有一個直接前驅和一個直接後繼。代表結構:棧、隊列
- 非線性結構:一個節點可能有多個直接前驅和多個直接後繼。代表結構:樹、圖
本篇主要講解棧和隊列的特點、區別,以及用數組和鏈表分別實現棧和隊列。
棧
堆棧(英語: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
就可以了。
總結
對於棧和隊列的簡單實現,其實上一篇就已經實現過了,所以本篇的重點是理解棧和隊列的工作方式、結構區別、使用區別等。棧和隊列是比較簡單的線性結構,但是簡單不代表用得少。實際上棧和隊列的應用非常廣泛,理解其工作原理,是使用好棧和隊列的第一步,也是最重要的一步。