循环队列的原理与代码实现

数组实现的普通队列低效的原因

  • 在出入队操作时,可能会引发数组的缩容与扩容。时间复杂度为O(n)
  • 在出队时,会将队首后的其他元素全部向前移位。时间复杂度为O(n)

       以上就是数组实现的普通队列效率低下的原因,而下面所讲到的循环队列则摒弃了第2条的批量移位操作,通过队首指针的移动实现了更高效的出队操作

循环队列实现原理

  1. 入队与出队

循环队列使用首(front)尾(tail)两个指针分别指向队首元素的索引队尾待插入位置的索引
出队时仅需将 front 向后移动一位即可;而入队时直接将入队元素放置在 tail 的索引位置上,tail 向后移动一位即可。
无论是出队还是入队,fronttail 都将会向后移动,当这两个指针超过数组容积时,使其再次回到数组头部。
以上操作只要保证出队队非空,入队队非满即可。

  1. 队空与队满

通过牺牲一个数组空间来区分队空和队满这两种情况:
       队空:front == tail
       队满:(tail + 1) % data.length == front
其中,+ 1就是所谓的浪费一个数组空间。
可以理解为:
       队空:可能发生在出队时,即当 front 向后移动与 tail 指向同一个位置时
       队满:可能发生在入队时,即当 tail 向后移动直到移动到 front 的前一个位置时

  1. 扩容与缩容

扩容与缩容就是开辟一个新的数组,该数组长度大小为原数组 n 倍,将原数组的所有元素复制到新数组中的操作
在这个过程中,原数组的索引要从 front 开始

  1. 指针对长度取模运算

无论是出队操作还是入队操作,只要满足出队队非空,入队队非满这一条件,fronttail 在向后移动时,当触及数组尾部,经过对数组长度的取模运算,就会回到数组头部,也正是因为这一特性,所以才叫做循环队列。

循环队列的代码实现

/**
 * 循环队列
 * @param <E>
 */
public class LoopQueue<E> implements Queue<E> {
    private E[] data; // 存储元素的数组
    private int front, tail; // 首尾指针
    private int size; // 队列内元素个数

    public LoopQueue(int capacity) {
        // + 1,因为循环队列会存在一个元素空间浪费的情况
        data = (E[]) new Object[capacity + 1];
        // 开始时,首尾指针都指向0的位置
        // 入队时,仅需维护尾指针;出队时,仅需维护首指针
        front = 0;
        tail = 0;
        size = 0;
    }

    public LoopQueue() {
        this(10);
    }

    @Override
    public void enqueue(E e) {
        // 队列若满则扩容为原容积2倍
        if ((tail + 1) % data.length == front)
            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 --;
        // 若队列中元素个数等于容积的1/4,且容积不为0时,缩容为原容积一半
        if (size == getCapacity() / 4 && getCapacity() != 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++)
            newData[i] = data[(i + front) % data.length];

        data = newData;
        front = 0;
        tail = size;
    }

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

    @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 result = new StringBuilder();
        result.append(String.format("Queue: size = %d, capacity = %d\n", size, getCapacity()));
        result.append("front [");
        for (int i = front; i != tail; i = (i + 1) % data.length) {
            result.append(data[i]);
            if ((i + 1) % data.length != tail)
                result.append(", ");
        }
        result.append("] tail");
        return result.toString();
    }

}

效率对比测试

返回队列出入队opCount个整型数据需要的时间
在这里插入图片描述
测试普通数组队列、循环队列、链表队列出入队 10000 个整型数据
在这里插入图片描述
最终运行结果如下
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自传丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值