队列

队列

队列跟数组一样,也是一种线性结构。相比数组,队列对应的操作是数组的子集,只能从一端(队尾)添加元素,也只能从一端(队首)取出元素。队列是一种先进先出的数据结构(FIFO)。

队列代码实现

首先,我们先定义一个队列需要用到的接口方法,因为队列的底层实现可以是多样的,只要实现了接口方法的,都可以称为队列。

队列接口定义

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 void enqueue(E e) {
        array.addLast(e);
    }

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

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

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

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

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

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("Queue: front [");
        for (int i = 0; i < getSize(); i++) {
            res.append(array.get(i));
            if (i != getSize() - 1) {
                res.append(", ");
            }
        }
        res.append("] tail");
        return res.toString();
    }
}

时间复杂度分析

int getSize() ,O(1) 级别

boolean isEmpty() , O(1) 级别

void enqueue(E e) , O(1) 级别,均摊

E dequeue() , O(n) 级别

E getFront() , O(1) 级别

循环队列代码

上述代码出队的时间复杂度为 O(n) 级别的,当数据量大的时候,性能比较差,如果想把复杂度变为 O(1) 级别的,可以用循环队列来实现。

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

    private E[] data;
    // 指向队首的位置
    private int front;
    // 指向队尾即将被添加元素的位置(不是队尾的位置,队尾的下一个位置)
    private int tail;
    // 队内实际存储元素的个数
    private int size;

    public LoopQueue(int capacity) {
        // 为防止元素装满时 front == tail ,所以故意空出一个位置
        // 但为了保证元素个数,把容量做 +1 处理
        data = (E[]) new Object[capacity + 1];
    }

    public LoopQueue() {
        this(10);
    }

    public int getCapacity() {
        // 创建的时候做了 +1 处理,这里应当 -1
        return data.length - 1;
    }

    @Override
    public void enqueue(E e) {
        // 当元素装满时,扩容
        if ((tail + 1) % data.length == front) {
            resize(getCapacity() * 2);
        }
        data[tail] = e;
        // 维护 tail 的位置,要循环处理,所以取模运算
        tail = (tail + 1) % data.length;
        size++;
    }

    /**
     * 扩容,均摊时间复杂度O(1)
     */
    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity + 1];
        for (int i = 0; i < size; i++) {
            // 从 front 开始,把所有元素复制到新对象中,索引从 0 开始
            newData[i] = data[(i + front) % data.length];
        }
        // 将当前数组对象指向新的数组对象
        data = newData;
        front = 0;
        tail = size;
    }

    @Override
    public E dequeue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
        }
        E ret = data[front];
        data[front] = null;// 把没用到的对象置null,利于GC释放内存
        front = (front + 1) % data.length;
        size--;
        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];
    }

    @Override
    public int getSize() {
        return size;
    }

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

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("LoopQueue: size = %d , capacity = %d\n", getSize(), getCapacity()));
        res.append("front [");
        // 这种遍历方式也是可以的
       /* for (int i = 0; i < size; i++) {
            res.append(data[(i+front)%data.length]);
            if (i != size - 1) {
                res.append(", ");
            }
        }*/

        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 性能比较

public class Main {

    public static void main(String[] args) {
        int opCount = 100000;
        ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
        LoopQueue<Integer> loopQueue = new LoopQueue<>();
        System.out.println("arrayQueue need time = " + testQueue(arrayQueue, opCount));
        System.out.println("loopQueue need time = " + testQueue(loopQueue, opCount));
    }

    /**
     * 测试队列的运行时间
     */
    private static double testQueue(Queue<Integer> queue, int opCount) {
        long startTime = System.nanoTime();
        Random random = new Random();
        for (int i = 0; i < opCount; i++) {
            queue.enqueue(random.nextInt(Integer.MAX_VALUE));
        }
        for (int i = 0; i < opCount; i++) {
            queue.dequeue();
        }
        long endTime = System.nanoTime();
        // 纳秒转成秒
        return (endTime - startTime) / 1000000000.0;
    }
}

运行结果:

arrayQueue need time = 60.940822538

loopQueue need time = 0.055889565

总结:LoopQueue 的性能比 ArrayQueue 好很多,因些 LoopQueue 是 O(1)级别的,ArrayQueue 是 O(n)级别的。