深入淺出ArrayDeque的設計和實現

說在前面,本篇文章不適合小白,需要先了解循環隊列的數組實現。

一、避輕就重,抓住核心

ArrayDeque容器類可能大家平時用得很少,但是其代碼的設計和實現真比我們平常經常用的ArrayList,Stack(Vector)要深刻的多!ArrayDeque容器類的方法裏有許多,但是核心的方法其實就4個,下面我們會具體介紹,但是要稍有一些耐心,因爲ArrayDeque涉及到的知識點還蠻多的。

二、鋪墊

ArrayDeque和LinkedList是Deque的兩個通用實現,並且官方更推薦使用AarryDeque用作棧和隊列。因此,要講ArrayDeque,首先要講Deque接口。Deque的含義是“double ended queue”,即雙端隊列,它既可以當作棧使用,也可以當作隊列使用。
下表列出了Deque與Queue相對應的接口:
在這裏插入圖片描述
下表列出了Deque與Stack對應的接口:
在這裏插入圖片描述
上述提到的方法,其實核心的就是我框出來的那4個,其他方法要麼是調用要麼就是被調用,內在邏輯在addFirst(),addLast(),removeFirst,removeLast中。

爲什麼選用這四個作爲代表呢?因爲這四個方法名起得好,具體原因後面再說,先來說ArrayDeque如何使用上面的函數實現隊列的,到這兒,大家可以對上面應該有了個印象,下面我們重新開始一段故事。

三、核心–循環隊列的數組實現

ArrayDeque的核心如果用一句話概括,就是循環隊列的數組實現,只不過加了雙向增刪數據。
那麼,首先,我們得先知道,循環隊列是如何用數組實現的。

3.1 循環隊列

自己網上百度,一定要弄懂循環隊列的數組實現原理,這對理解ArrayDeque很重要!
下面這個是我基於數組實現的循環隊列。

/*
   數組實現的雙端隊列:本質上是循環隊列
   head指向隊首元素,tail指向隊尾元素的下一空位
 */
public class ArrayBasedCircularQueue {
    private int head=0;
    private int tail=0;
    protected int[] array;
    //循環隊列的大小要比實際的要大1
    public ArrayBasedCircularQueue(int capacity){
        array=new int[capacity+1];
    }

    //入隊
    public void enQueue(int item){
        //判斷是否隊滿
       if((tail+1)%(array.length)==head){
          System.out.println("The queue is Full!");
          return;
        }
        array[tail]=item;
        tail=(tail+1)%(array.length);
    }

    //出隊
    public int deQueue(){
       //判斷是否隊空
       if(tail==head){
           System.out.println("The queue is Empty!");
           return -1;
       }
       int temp=head;
       head=(head+1)%(array.length);
       return array[temp];
    }

    //打印隊列所有元素
    public void print(){
       for (int i = head; i % array.length != tail; i = (i + 1) % array.length){
           System.out.println(array[i]);
       }
    }

    public static void main(String[] args) {
        ArrayBasedCircularQueue queue=new ArrayBasedCircularQueue(3);
        queue.enQueue(1);
        queue.enQueue(2);
        queue.enQueue(3);
        //隊滿
        queue.enQueue(4);
        queue.print();

        //出隊
        System.out.println("出隊:");
        queue.deQueue();
        queue.print();

        //入隊
        System.out.println("入隊:");
        queue.enQueue(4);
        queue.print();

        //出隊
        queue.deQueue();

        //入隊
        System.out.println("入隊:");
        queue.enQueue(5);
        queue.print();

    }

}

學習編程,敲代碼是唯一的學習方法。上述代碼,希望你能敲一遍。對於上面代碼,不知你沒有發現一個問題,這個問題就是“循環隊列的大小爲什麼要比實際的要大1。”,也就是說“爲什麼循環隊列總有一個位置是空着的。”,這個原因,我理解是由於“head指向隊首元素,tail指向隊尾元素的下一空位”,那麼爲什麼“tail要指向隊尾元素的下一空位”,而不是“指向隊尾元素呢?” ,這裏,tail要指向隊尾元素的下一空位不是我故意這麼設計的,JAVA容器類也是這麼實現的,原因是什麼呢?大家感興趣,可以好好琢磨琢磨。

3.2 循環隊列的雙向增刪

上面,我們實現的循環隊列,如果把循環隊列看成是一個圓,我們只是實現了順時針的增刪元素,順時針的增刪元素,enQueue()deQueue()方法,其實就對應着addLast()removeFirst()(顧命思義一下,動動腦筋,不難想到),對於ArrayDeque這種雙端隊列,我們還需要實現逆時針的增刪元素,那就是addFirst()removeLast()

/*
   數組實現的雙端隊列:本質上是循環隊列
   head指向隊首元素,tail指向隊尾元素的下一位
 */
public class ArrayBasedDeque {
    private int head=0;
    private int tail=0;
    protected int[] array;

    //循環隊列的大小要比實際的要大1
    public ArrayBasedDeque(int capacity){
        array=new int[capacity+1];
    }

    //入隊:從前往後 -->
    /*
     這在ArrayBasedDeque裏稱之爲addLast();
     */
    public void enQueue(int item){
        //判斷是否隊滿
        if((tail+1)%(array.length)==head){
            System.out.println("The queue is Full!");
            return;
        }
        array[tail]=item;
        tail=(tail+1)%(array.length);
    }

    //入隊:從後往前 <--
    public void AddFirst(int item){
       if((tail+1)%(array.length)==head){
           System.out.println("The queue is Full!");
           return;
       }
       array[head=(head - 1) & (array.length - 1)]=item;
    }

    //出隊:先進先出
    /*
     這在ArrayBasedDeque裏稱之爲removeFirst();
     */
    public int deQueue(){
        //判斷是否隊空
        if(tail==head){
            System.out.println("The queue is Empty!");
            return -1;
        }
        int temp=head;
        head=(head+1)%(array.length);
        return array[temp];
    }

    //出隊:先進先出
    public int removeLast(){
        if(tail==head){
            System.out.println("The queue is Empty!");
            return -1;
        }
        return array[tail=(tail-1)& (array.length - 1)];
    }

    //打印隊列所有元素
    public void print(){
        for (int i = head; i % array.length != tail; i = (i + 1) % array.length){
            System.out.println(array[i]);
        }
    }
}

上述代碼,還是得你敲一遍。我留一個問題,看你是否掌握了,addLast()、removeFirst()實現的隊列,和AddFirst(),removeLast()實現的隊列在存儲元素時,他們在數組中的順序是什麼樣的?

所以,到這,如果你弄懂了循環隊列的數組實現,並且是如何支持雙向增刪數據,那麼你應該不難想出,怎麼用addFirst(),addLast(),removeFirst,removeLast這四個方法來實現出棧,這需要你自己動手。

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