欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

队列

程序员文章站 2022-05-04 16:25:59
...

队列(Queue)是一种线性数据结构。相关操作也比数组简单的多,但是队列与栈不同的是,队列只能从一段(队尾)添加元素,从另一端(队首)取出元素,即先进先出(FIFO)。

队列

实现自己的队列

首先定义一个顶层接口:

/**
 * @author Dongguabai
 * @date 2018/9/13 9:16
 */
public interface Queue<E> {

    /**
     * 入队
     * @param e
     */
    void enqueue(E e);

    /**
     * 出队
     * @return
     */
    E dequeue();

    /**
     * 查看队首的元素
     * @return
     */
    E getFront();

    /**
     * 元素个数
     * @return
     */
    int getSize();

    /**
     * 是否为空
     * @return
     */
    boolean isEmpty();
}

基于动态数组实现的队列(关于动态数组可参看:https://blog.csdn.net/Dongguabai/article/details/82189286):

/**
 * @author Dongguabai
 * @date 2018/9/13 9:20
 */
@ToString
public class ArrayQueue<E> implements Queue<E>{

    private MyArrayList<E> array;

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

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

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

}

从上面的代码也可以看出一个问题,在执行dequeue()方法时候,在队列的元素数量很大的时候是特别影响性能的,因为删除队首的元素,后面所有的元素都要向前挪一位,即时间复杂度为O(n)。

队列

当a元素出队后,后面的b、c、d、e元素需要向前挪一位,同时size--。

解决办法就是我们可以使用front记录队首的元素位置,使用tail记录下次插入的元素的位置,这样每次出队列的时候,我们只需要维护front即可,即front++。

队列

于是就出现了循环队列。

当队列为空的时候,front指向0,tail也指向0,即队列为空的时候front==tail:

队列

这时候新增一个元素a,front不变,tail++,以此类推:

队列

队列

如果发生了出队操作,只需要fron++,tail不变:

队列

以此类推,那为什么叫循环队列呢,因为如果此时队列元素情况如下:

队列

如果再新增一个元素f,此时tail就不能++了,而此时队列前面还有空着的空间,这就可以成为循环队列,即数组可以看成一个环了。也就是说tail不能简单的执行++操作,应该是(tail+1)%capacity,在这里就是0,即此时tail应该指向索引为0的位置:

队列

这样的话,再有新的元素进来了就将进入索引为0 的位置,tail继续加一:

队列

但是这样问题就来了,之前说过当front==tail即表示队列为空,如果新的元素进入索引为1的位置,那么tail++,而此时front==tail,这样当front==tail即表示队列为空这个条件就不成立了,因为此时队列已满,这个不是我们所希望看到的。所以可以定义tail+1==front即表示队列中的数组已满,准确来说应该是(tail+1)%capacity==front,此时可以进行扩容处理,这样的话也就是说我们“有意识的浪费了一个空间”。

接下来实现自己的循环队列:

/**
 * 循环队列
 *
 * @author Dongguabai
 * @date 2018/9/13 10:27
 */
@ToString
public class LoopQueue<E> implements Queue<E> {

    private E[] data;
    private int front;
    private int tail;

    //也可以不需要size
    private int size;


    public LoopQueue(int capacity) {
        //需要浪费一个空间
        data = (E[]) new Object[capacity + 1];
        front = 0;
        tail = 0;
        size = 0;
    }

    public LoopQueue() {
        this(10);
    }

    public static void main(String[] args) {

        LoopQueue<Integer> queue = new LoopQueue<>();
        for (int i = 0; i < 10; i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }

    public int getCapacity() {
        //需要浪费一个空间
        return data.length - 1;
    }

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

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

    @Override
    public void enqueue(E e) {

        if ((tail + 1) % data.length == front) {
            //浪费一个空间,即扩容就是实际容量*2
            resize(getCapacity() * 2);
        }
        data[tail] = e;
        tail = (tail + 1) % data.length;
        size++;
    }

    @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;
        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];
    }

    private void resize(int newCapacity) {

        E[] newData = (E[]) new Object[newCapacity + 1];
        for (int i = 0; i < size; i++) {
            newData[i] = data[(i + front) % data.length];
        }
        data = newData;
        front = 0;
        tail = size;
    }

 

上一篇: 队列

下一篇: 队列