队列是一种先进先出的线性数据结构,只能观察到队首元素,首先创建了一个Queue接口类如下:

public interface Queue<E> {  //队列接口
  int getSize();           //获取队列大小
  boolean isEmpty();       //判断队列是否为空
  void enqueue(E e);       //入队
  E dequeue();             //出对
  E getFront();            //获取队首元素
 }

实现的动态数组来实现底层队列的数据结构,具体过程如下:

public class ArrayQueue<E> implements Queue<E>{   
  private Array<E> array;    //声明数组用于保存数据
  
  public ArrayQueue() {         //无参构造函数,以默认容量生成队列
   array = new Array<>();
  }
  
  public ArrayQueue(int capacity) { //有参构造函数,以传入容量生成队列
   array = new Array<>(capacity);
  }
  
  @Override
  public int getSize() {             //返回队列大小
   return array.getSize();
  } @Override
  public boolean isEmpty() {        //判断队列是否为空
   return array.isEmpty();
  }
  
  public int getCapacity() {        //返回队列容量,也就是底层数组容量
   return array.getCapacity();
  } @Override
  public void enqueue(E e) {        //入队,也就是向数组末尾加入元素
   array.addLast(e);
  } @Override
  public E dequeue() {              //出对,也就是向数组头部删除元素
   return array.removeFirst();
  } @Override
  public E getFront() {            //获取队首元素
   return array.getFirst();    
  }
  
  //重写toString方法,便于打印观察数据结构
     @Override
     public String toString(){
         StringBuilder res = new StringBuilder();
         res.append("Queue: ");
         res.append("Front [");
         for(int i = 0 ; i < array.getSize() ; i ++){
             res.append(array.get(i));
             if(i != array.getSize() - 1)
                 res.append(", ");
         }
         res.append("] Tail");
         return res.toString();
     }
 }

由于上面这种实现队列的方式出队时间复杂度为O(n),入队是往数组末尾添加元素时间复杂度为O(1),因此可以利用循环队列解决出队时间复杂度为O(n)的问题。为了实现循环队列,不能够使用上面的动态数组来实现。循环队列需要front指向头,tail指向尾部下一个位置,队列为空时front与tail相等,出队一个,front往后挪一下,元素不动,当尾部添加满时,可以往数组前面空位置添加,直到(tail + 1) % data.length == front 表明队列已满应该扩容,实际上空出一个位置,因为直接用front与tail是否相等判断会无法避免排除队列为空的情况,所以用了tail + 1 与front判断队列是否为满(牺牲了一个位置),由于是循环队列,tail可能会跑到front前面,所以需要tail每次加1挪动后模上数组长度。下面是具体实现过程

public class LoopQueue<E> implements Queue<E>{
  private E[] data;         //声明泛型数组用于保存数组
  private int front, tail;  //声明队首和队尾指示
  private int size;         //声明队列尺寸大小
  
  //有参构造函数,以传入容量大小+1(因为需要牺牲一个位置,所以要比用户输入容量大1)生成数组
  public LoopQueue(int capacity) {
   data = (E[]) new Object[capacity + 1];
   front = 0;
   tail = 0;
   size = 0;
  }
  
  //无参构造函数,默认生成容量为10的数组
  public LoopQueue() {
   this(10);
  }
  
  //获取队列容量,由于在构造时加1,需要返回时减去1,展现给用户的是输入的容量
  public int getCapacity() {
   return data.length -1;
  }
  
  @Override
  public boolean isEmpty() { //判断队列是否为空
   return front == tail;
  }
  
  @Override
  public int getSize() {     //获取队列大小
   return size;
  }
  
  //入队操作,利用自定义resize函数扩容,实现动态数据结构
  @Override
  public void enqueue(E e) {
   //判断队列是否已满,已满就以两倍当前容量扩容
   if((tail + 1) % data.length == front)
    resize(getCapacity() * 2);
   data[tail] = e;                  //向队尾添加相应元素
   tail = (tail + 1) % data.length;    //将tail指向下一个位置
   size++;                             //维护size,将其加1
  } //出队操作,利用自定义resize函数缩容,避免内存浪费
  @Override
  public E dequeue() {
   //如果队列已空,再进行出队就抛出异常
   if(isEmpty())
    throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
   
   E ret = data[front];      //保存出队元素
   data[front] = null;
   front = (front + 1) % data.length;  //将front往后挪一位
   size--;                             //维护size, 将其减一
   //如果数组大小已经等于容量的1/4 并且数组容量的1/2不等于0,将数组容量缩减到当前容量的1/2
   if(size == getCapacity() / 4 && getCapacity() / 2 != 0)
    resize(getCapacity() / 2);
   
   return ret;        //返回出对元素
  }
  
  //获取头部元素
  @Override
  public E getFront() {
   if(isEmpty())
    throw new IllegalArgumentException("Queue is empty.");
   return data[front];
  }
  //扩容函数是内部功能函数,将其是私有化
  private void resize(int capacity) {
   //以传入容量大小生成新数组
   E [] newData = (E[]) new Object[capacity + 1];
   //从front开始遍历将队列数据复制到新数组中
   for(int i = 0; i < size; i++) {
    newData[i] = data[(i + front) % data.length];
   }
   data = newData;  //原数组指向新数组
   front = 0;   //front指向0
   tail = size;  //tail指向末尾的下一个位置
  }
  
  //重写toString方法,便于打印队列数据结构
  @Override
  public String toString() {
   StringBuilder res = new StringBuilder();
   res.append(String.format("Queue: size = %d, capacity = %d\n", size, getCapacity()));
   res.append("Front [");
   for(int i = front; i != tail; i = (i+1) % data.length) {
    res.append(data[i]);
    if ( (i+1) % data.length != tail) {
     res.append(", ");
    }
   }
   res.append("] Tail");
   return res.toString();
  }
 }

由于ArrayQueue与LoopQueue的出队时间复杂度不一致,一个是O(n),另一个是O(1),下面写了一个简单的测试程序比较了一下两者运行时间,测试代码如下

import java.util.Random;
public class Main {
 public static void main(String[] args) {
   int opCount = 100000;
   ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
   double time1 = testQueue(arrayQueue, opCount);
   System.out.println("ArrayQueue comsuming time: " + time1);
   
   LoopQueue<Integer> loopQueue = new LoopQueue<>();
   double time2 = testQueue(loopQueue, opCount);
   System.out.println("LoopQueue comsuming time: " + time2);
  }
  
  public static double testQueue(Queue<Integer> q, int opCount) {
   long startTime = System.nanoTime();
   
   Random random = new Random();
   for(int i=0; i < opCount; i++) {
    q.enqueue(random.nextInt(Integer.MAX_VALUE));
   }
   for(int i=0; i < opCount; i++) {
    q.dequeue();
   }
   long endTime = System.nanoTime();
   return (endTime - startTime) / 1000000000.0;
  }
 }

每人电脑配置不一样,所以结果不一样,但是二者差异肯定会体现出来的,上面是两种队列分别进行10万次入队和出队的消耗时间测试,电脑测试结果为

Java Queue 的栈 java queue实现_数组

差异结果有上百倍之多,这就说明循环队列确实是将出队的时间复杂度降低。