深入浅出ArrayDeque的设计和实现

说在前面,本篇文章不适合小白,需要先了解循环队列的数组实现。

一、避轻就重,抓住核心

ArrayDeque容器类可能大家平时用得很少,但是其代码的设计和实现真比我们平常经常用的ArrayList,Stack(Vector)要深刻的多!ArrayDeque容器类的方法里有许多,但是核心的方法其实就4个,下面我们会具体介绍,但是要稍有一些耐心,因为ArrayDeque涉及到的知识点还蛮多的。

二、铺垫

ArrayDeque和LinkedList是Deque的两个通用实现,并且官方更推荐使用AarryDeque用作栈和队列。因此,要讲ArrayDeque,首先要讲Deque接口。Deque的含义是“double ended queue”,即双端队列,它既可以当作栈使用,也可以当作队列使用。
下表列出了Deque与Queue相对应的接口:
深入浅出ArrayDeque的设计和实现_第1张图片
下表列出了Deque与Stack对应的接口:
深入浅出ArrayDeque的设计和实现_第2张图片
上述提到的方法,其实核心的就是我框出来的那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这四个方法来实现出栈,这需要你自己动手。

你可能感兴趣的:(JAVA容器类)