1. 程式人生 > >Java雙向佇列Deque棧與佇列

Java雙向佇列Deque棧與佇列

Java中實際上提供了java.util.Stack來實現棧結構,但官方目前已不推薦使用,而是使用java.util.Deque雙端佇列來實現佇列與棧的各種需求.如下圖所示java.util.Deque的實現子類有java.util.LinkedListjava.util.ArrayDeque.顧名思義前者是基於連結串列,後者基於資料實現的雙端佇列.

總體介紹

要講棧和佇列,首先要講Deque介面。Deque的含義是“double ended queue”,即雙端佇列,它既可以當作棧使用,也可以當作佇列使用。下表列出了Deque與Queue相對應的介面:

下表列出了Deque與Stack對應的介面:

上面兩個表共定義了Deque的12個介面。新增,刪除,取值都有兩套介面,它們功能相同,區別是對失敗情況的處理不同。一套介面遇到失敗就會丟擲異常另一套遇到失敗會返回特殊值(false或null)。除非某種實現對容量有限制,大多數情況下,新增操作是不會失敗的。雖然Deque的介面有12個之多,但無非就是對容器的兩端進行操作,或新增,或刪除,或檢視。明白了這一點講解起來就會非常簡單。

ArrayDeque

從名字可以看出ArrayDeque底層通過陣列實現,為了滿足可以同時在陣列兩端插入或刪除元素的需求,該陣列還必須是迴圈的,即迴圈陣列(circular array),也就是說陣列的任何一點都可能被看作起點或者終點。ArrayDeque是非執行緒安全

的(not thread-safe),當多個執行緒同時使用的時候,需要程式設計師手動同步;另外,該容器不允許放入null元素

上圖中我們看到,head指向首端第一個有效元素tail指向尾端第一個可以插入元素的空位。因為是迴圈陣列,所以head不一定總等於0,tail也不一定總是比head大。

addFirst()

針對首端插入實際需要考慮:1.空間是否夠用,以及2.下標是否越界的問題。上圖中,如果head為0之後接著呼叫addFirst(),雖然空餘空間還夠用,但head為-1,下標越界了。下列程式碼很好的解決了這兩個問題。

  public void addFirst(E e) {
    if (e == null)
        throw new NullPointerException();
    //下標越界問題解決方案
    elements[head = (head - 1) & (elements.length - 1)] = e;
    //容量問題解決方案
    if (head == tail)
        doubleCapacity();
}

上述程式碼我們看到,空間問題是在插入之後解決的,因為tail總是指向下一個可插入的空位,也就意味著elements陣列至少有一個空位,所以插入元素的時候不用考慮空間問題。

下標越界的處理解決起來非常簡單,head = (head - 1) & (elements.length - 1)就可以了,這段程式碼相當於取餘,同時解決了head為負值的情況。因為elements.length必需是2的指數倍(建構函式初始化邏輯保證),elements - 1就是二進位制低位全1,跟head - 1相與之後就起到了取模的作用,如果head - 1為負數(其實只可能是-1),則相當於對其取相對於elements.length的補碼。

下面再說說擴容函式doubleCapacity(),其邏輯是申請一個更大的陣列(原陣列的兩倍),然後將原陣列複製過去。過程如下圖所示:

 

圖中我們看到,複製分兩次進行,第一次複製head右邊的元素,第二次複製head左邊的元素。

private void doubleCapacity() {
    assert head == tail;
    int p = head;
    int n = elements.length;
    int r = n - p; // number of elements to the right of p
    int newCapacity = n << 1;
    if (newCapacity < 0)
        throw new IllegalStateException("Sorry, deque too big");
    Object[] a = new Object[newCapacity];
    System.arraycopy(elements, p, a, 0, r);
    System.arraycopy(elements, 0, a, r, p);
    elements = a;
    head = 0;
    tail = n;
}

addLast()

addLast(E e)的作用是在Deque的尾端插入元素,也就是在tail的位置插入元素,由於tail總是指向下一個可以插入的空位,因此只需要elements[tail] = e;即可。插入完成後再檢查空間,如果空間已經用光,則呼叫doubleCapacity()進行擴容。與first比較類似就不多分析了.

其他操作也是差不多的方式,唯一麻煩的head與tail位置轉換也用取餘巧妙的化解了.

LinkedList

LinkedList實現了Deque介面,因此其具備雙端佇列的特性,由於其是連結串列結構,因此不像ArrayDeque要考慮越界問題,容量問題,那麼對應操作就很簡單了,另外當需要使用棧和佇列是官方推薦的是ArrayDeque,因此這裡不做多的分析.