work hard work smart

专注于Java后端开发。 不断总结,举一反三。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

队列(动态数组实现自定义队列)

Posted on 2021-02-07 22:18  work hard work smart  阅读(162)  评论(0编辑  收藏  举报

1、什么是队列

队列也是一种线性结构

相比数组,队列对应的操作是数组的子集

只能从一端(队尾)添加元素,只能从另一端(队首)取出元素。

队列是一种先进先出的数据结构( 先到先得)

First In First Out(FIFO)

 

2、自定义队列

1) 定义接口

public interface IQueue<E> {
    int getSize();  // 时间复杂度 O(1) 均摊
    boolean isEmpty(); // 时间复杂度 O(1)
    void enqueue(E e);   // 时间复杂度 O(1) 
    E dequeue();  // 时间复杂度 O(n) 
    E getFront(); // 时间复杂度 O(1)
}

  

2、自定义队列实现

基于自定义数组ArrayQueue

public class ArrayQueue<E> implements IQueue<E> {

    private CustomArray<E> array;

    public ArrayQueue(int capacity){
        array = new CustomArray<E>(capacity);
    }

    public ArrayQueue(){
        array = new CustomArray<E>();
    }

    public int getSize() {
        return array.getSize() ;
    }

    public boolean isEmpty() {
        return array.isEmpty();
    }

    public int getCapacity(){
        return  array.getCapacity();
    }

    public void enqueue(E e) {
        array.addLast(e);
    }

    public E dequeue() {
        return array.removeFirst();
    }

    public E getFront() {
        return array.getFirst();
    }

    @Override
    public  String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("Queue"));
        sb.append("队首 [");
        for(int i = 0; i< array.getSize(); i++){
            sb.append(array.get(i));
            if(i != array.getSize() -1){
                sb.append(", ");
            }
        }
        sb.append("] 队尾");
        return  sb.toString();
    }

    public static void main(String[] args) {
        ArrayQueue<Integer> queue = new ArrayQueue<Integer>();
        for(int i = 0; i < 10; i++){
            queue.enqueue(i);
        }
        System.out.println(queue); // 输出: Queue队首 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 队尾

        queue.dequeue();
        System.out.println("移除一个队首元素=> " + queue); // 输出: 移除一个队首元素=> Queue队首 [1, 2, 3, 4, 5, 6, 7, 8, 9] 队尾

        queue.dequeue();
        System.out.println("移除一个队首元素=> " + queue); //输出:  移除一个队首元素=> Queue队首 [2, 3, 4, 5, 6, 7, 8, 9] 队尾
    }
}

  

3、循环队列

前面的自定义队列,出队列dequeue方法的时间复杂度为O(n), 这个复杂度产生的原因?

 

 

能不能在删除队首元素后,后面的元素不往前移动1位呢?这里就可以用到循环队列

 

循环队列实现

public class LoopQueue<E> implements IQueue<E> {

    private E[] data;
    //队首元素的索引
    private int front;
    //队尾元素的索引+1
    private int tail;
    private int size;
    public LoopQueue(int capacity){
        //循环队列,浪费1个空间
        data = (E[]) new Object[capacity +1];
        front = 0;
        tail = 0;
        size = 0;
    }

    public LoopQueue(){
        this(10);
    }

    public  int getCapacity(){
        return  data.length -1;
    }


    public int getSize() {
        return size;
    }

    public boolean isEmpty() {
        return front == tail;
    }

    public void enqueue(E e) {
        if((tail +1) % data.length == front){
            resize(getCapacity() *2);
        }
        data[tail] = e;
        tail = (tail + 1) % data.length;
        size++;

    }

    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;
        size --;
        if(size == getCapacity() / 4  && getCapacity() / 2 != 0){
            resize( getCapacity() / 2);
        }
        return ret;
    }

    private void resize(int newCapaCity) {
        E[] newData = (E[])new Object[newCapaCity +1];
        for(int i = 0; i < size; i++){
            //i索引所在的数据,可能发生了偏移
            newData[i] = data[(i+ front) % data.length];
        }
        data = newData;
        front = 0;
        tail = size;
    }



    public E getFront() {
        if(isEmpty()){
            throw new IllegalArgumentException("Queue is empty.");
        }
        return data[front];
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("Loop Queue size= %d, capacity = %d  ", this.size, getCapacity()));
        sb.append("队首 [");
        for(int i = front; i != tail; i = (i +1) % data.length ){
            sb.append(data[i]);
            //不是最后一个元素
            if( (i + 1) % data.length != tail){
                sb.append(", ");
            }
        }
        sb.append("] 队尾");
        return  sb.toString();
    }

    public static void main(String[] args) {
        LoopQueue<Integer> queue = new LoopQueue<Integer>();
        for(int i = 0; i < 5; i++){
            queue.enqueue(i);
        }
        System.out.println(queue); // 输出: Loop Queue size= 5, capacity = 10  队首 [0, 1, 2, 3, 4] 队尾

        queue.dequeue();
        System.out.println("移除一个队首元素=> " + queue); // 输出: 移除一个队首元素=> Loop Queue size= 4, capacity = 10  队首 [1, 2, 3, 4] 队尾

        queue.dequeue();
        System.out.println("移除一个队首元素=> " + queue); //输出: 移除一个队首元素=> Loop Queue size= 3, capacity = 10  队首 [2, 3, 4] 队尾

        queue.dequeue();
        System.out.println("移除一个队首元素=> " + queue); //输出: 移除一个队首元素=> Loop Queue size= 2, capacity = 5  队首 [3, 4] 队尾

        queue.dequeue();
        System.out.println("移除一个队首元素=> " + queue); //输出:  移除一个队首元素=> Loop Queue size= 1, capacity = 2  队首 [4] 队尾

        queue.dequeue();
        System.out.println("移除一个队首元素=> " + queue); //输出:  移除一个队首元素=> Loop Queue size= 0, capacity = 1  队首 [] 队尾
    }
}  

总结: 循环队列实现了dequeue为O(1) 均摊 的时间复杂度。

 

4、数组队列和循环队列的比较

编写如下测试代码:

public class Test {

    //测试实验队列q运行opCount个enqueue和dequeue操作所需要的时间,单位: 秒
    private static double testQueue(IQueue<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;
    }

    public static void main(String[] args) {
        int opCount = 100000;

        ArrayQueue<Integer> arrayQueue = new ArrayQueue<Integer>();
        double time1 = testQueue(arrayQueue, opCount);
        System.out.println("数组队列ArrayQueue, time: " + time1 + "秒");

        LoopQueue<Integer> loopQueue = new LoopQueue<Integer>();
        double time2 = testQueue(loopQueue, opCount);
        System.out.println("循环队列LoopQueue, time: " + time2 + "秒");

    }


}  

输出结果: 

数组队列ArrayQueue, time: 4.540920463秒
循环队列LoopQueue, time: 0.054913962秒  

可以看到循环队列比数字队列时间相差近100倍。