1

队列(Queue)

  • 是一种线性结构
  • 只能从队尾添加元素,称为入队(Enqueue);
  • 只能从队首取出元素,称为出队(Dequeue);

队列的逻辑结构
先进先出的特性(FIFO-first in first out):最先插入的元素最先出来.
back:当前队尾的索引(下面我们用tail代替)
front:当前队首的索引

普通数组队列的问题

取元素时只能从数组首端,也就是索引为0的位置取出

存放元素只能从数组末端添加,
而且队首到队尾之间的元素不能存在空闲,要一个紧挨着一个

但会存在一个缺陷,想象一下:
**如果从队首取出一个元素,那么索引为0的位置就空了出来,
必须把后继的所有元素遍历向前挪一个位置;**

这样操作时间复杂度为O(n):
n代表着数组中的元素个数,这个算法的运行时间与元素个数是一个线性的关系,也就是说nums中的元素个数越多,这个方法的运行时间就越长.

为了解决这个问题,需要抽象一下:

循环队列示例
循环队列的机制:
front:当前队首的索引
tail:当前队尾的索引
capcity:容量,由用户定义数组可容纳多少元素

把整个数组想象成一个首尾相连的闭环,队首元素出队后不再挪动其他元素,
而是维护一个front的变量表示当前队首的位置;

当数组末端位置占满后,新入队的元素添加到数组前端空虚的位置,
维护一个tail的变量表示当前队尾的位置,tail = 当前末端索引+1 % arr.length;

但是注意当这个闭环要满的时候,最后一个位置也就是tail所指的位置不能再插入元素了,
防止front == tail,此时就应该扩容,所以获取容量的时候应该忽略这个空闲的位置capacity = arr.length -1

设计了以上措施后front == tail的情况只能是队列为空

(tail + 1) % arr.length == front 表示队列已满

如此,dequeue()的复杂度就成了O(1) (均摊)

下面是具体代码,包含扩容机制和时间复杂度测试:

定义接口,使用泛型,让它可以存储任意类型的数据.

public interface Queue<E> {
    //获取有效的元素个数
    int getSize();
    //查看队列是否为空
    boolean isEmpty();
    //入队
    void enqueue(E e);
    //出队
    E dequeue();
    //查看队首的元素
    E getFront();
}

实现类:

public class LoopQueue<E> implements Queue<E> {
    //队列元素
    private E[] data;
    //队首, 队尾的位置
    private int front, tail;
    private int size;

    //根据用户指定的容量初始化队列
    public LoopQueue(int capacity){
        //需要预留出一个空间
        data = (E[])new Object[capacity + 1];
        //初始化成员变量
        front = 0;
        tail = 0;
        size = 0;
    }
    //无参构造
    public LoopQueue(){
        //调用有参构造,设置默认容量为10
        this(10);
    }

    //获取队列的容量
    public int getCapacity(){
        //在数组中有一个空间始终会被空闲
        return data.length - 1;
    }

    //获取有效元素的个数
    @Override
    public int getSize() {
        return size;
    }

    //查看队列是否为空
    @Override
    public boolean isEmpty() {
        return front == tail;
    }

    //向队列尾部添加元素(入队)
    @Override
    public void enqueue(E e) {
        //判断队列是否已满
        if ((tail + 1) % data.length == front)
            //扩容为当前容量的2倍
            resize(getCapacity() * 2);

        data[tail] = e;
        //维护tail值
        tail = (tail + 1) % data.length;
        size ++;
    }

    private void resize(int newCapacity){
        //仍然需要预留一个空间
        E[] newData = (E[]) new Object[newCapacity + 1];
        //将元素复制到新的数组中
        for (int i = 0; i < size; i++)
            // data中的元素相对于newData中的元素,
            // 索引值是存在front这么多的偏移的(没懂)
            // % data.length是为了防止越界
            newData[i] = data[(i + front) % data.length];

        data = newData;//data指向新的数组
        front = 0;//队首又回到了0号索引
        tail = size;//对尾便是size
    }

    //队首元素出队操作
    @Override
    public E dequeue() {
        //如果队列是空的,抛出异常
        if (isEmpty())
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");

        //保存出队的元素
        E returnElement = data[front];
        //将它的引用变为null,垃圾回收器就会回收这片空间
        data[front] = null;
        //维护front值
        front = (front + 1) % data.length;
        size --;
        // 如果当前数组的容量只有1/4被利用到,就进行缩容
        // Lazy的思想,延迟缩容,防止复杂度震荡
        if (size == getCapacity() / 4 && getCapacity() / 2 != 0)
            resize(getCapacity() / 2);
        return returnElement;
    }

    //查看队首的元素
    @Override
    public E getFront() {
        //如果队列是空的,抛出异常
        if (isEmpty())
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
        //返回front位置的元素即可
        return data[front];
    }

    @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();
    }

}
//测试用例
public static void main(String[] args) {
        LoopQueue<Integer> queue = new LoopQueue<>();
        for (int i = 0; i < 10; i++){
            //将0~9的数字入队
            queue.enqueue(i);
            System.out.println(queue);

            //每隔3个数字,做一次出队操作
            if (i % 3 == 2){
                queue.dequeue();
                System.out.println(queue);
            }
        }

}
--------console---------
Queue: size = 1 , capacity = 10
front [0] tail
Queue: size = 2 , capacity = 10
front [0, 1] tail
Queue: size = 3 , capacity = 10 //触发缩容
front [0, 1, 2] tail
Queue: size = 2 , capacity = 5 
front [1, 2] tail
Queue: size = 3 , capacity = 5
front [1, 2, 3] tail
Queue: size = 4 , capacity = 5
front [1, 2, 3, 4] tail
Queue: size = 5 , capacity = 5
front [1, 2, 3, 4, 5] tail
Queue: size = 4 , capacity = 5
front [2, 3, 4, 5] tail
Queue: size = 5 , capacity = 5 
front [2, 3, 4, 5, 6] tail
Queue: size = 6 , capacity = 10 //触发扩容
front [2, 3, 4, 5, 6, 7] tail
Queue: size = 7 , capacity = 10
front [2, 3, 4, 5, 6, 7, 8] tail
Queue: size = 6 , capacity = 10
front [3, 4, 5, 6, 7, 8] tail
Queue: size = 7 , capacity = 10
front [3, 4, 5, 6, 7, 8, 9] tail

与普通数组队列的执行时间对比:

public class Main {

    //测试使用q运行opCount个enqueue和dequeue操作所需要的时间,单位:秒
    private static double testQueue(Queue<Integer> q,int opCount){

        long startTime = System.nanoTime();

        Random random = new Random();
        for (int i = 0; i < opCount; i++)
            //生成0 ~ Integer最大值之间的随机数入队
            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<>();
        double time1 = testQueue(arrayQueue,opCount);
        System.out.println("ArrayQueue, time:" + time1 + " s");

        LoopQueue<Integer> loopQueue = new LoopQueue<>();
        double time2 = testQueue(loopQueue, opCount);
        System.out.println("LoopQueue, time:" + time2 + " s");
    }
}

-----------console------------
ArrayQueue, time:3.306405 s
LoopQueue, time:0.0124581 s //优化了太多妙啊

结合自己的理解记录了学习的过程,希望对你有帮助
图片来自网络


打了个冷颤
19 声望0 粉丝

且听风吟