数据结构:队列之循环队列

背景

循环队列出队和入队的均摊复杂度均为O(1)级别,性能比普通队列更高
普通队列的入队复杂度为O(1),但出队复杂度为O(n),因为当出队时,后面的数组有个前移一位的过程.

实现原理

语言描述

实现原理其实也比较简单,就是数组维护队首front和队尾tail索引.当出队时,无需移动数据位置.当入队时,到达数组底部后,通过取余回到数组前端.索引类似在一个数组环上移动,这也是循环一名的由来.
当front和tail相同时,即代表队列为空.当tail的下一个为front时,即代表队列已满,需要扩容.

在这里插入图片描述

代码实现

package xyz.yq.ds.queue;

/**
 * 循环队列
 * <br> 减少出队时的时间复杂度: 传统队列出队的时间复杂度为O(n),中间会有一个所有元素前移的过程
 * <br> 设计理念: 类似时间环,当tail和head相等时,队列为空;当tail-1=head时,队列为满
 *
 * @author yi qiang
 * @date 2022/5/15 22:15
 */
@SuppressWarnings("all")
public class LoopQueue<E> implements Queue<E> {

    /**
     * 数组
     */
    E[] data;

    private int front = 0;
    private int tail = 0;
    private int size = 0;

    /**
     * 判断队列是否已满的时候会浪费一个空间,故需要+1
     * <br> 比如用户希望容量是10个,那实际要是11个才能满足需要
     *
     * @param capacity 容量
     */
    public LoopQueue(int capacity) {
        data = (E[]) new Object[capacity + 1];
    }

    public LoopQueue() {
        this(10);
    }


    public int getCapacity() {
        //因为会浪费掉一个空间,实际容纳的数据量要-1
        return data.length - 1;
    }

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

    /**
     * 当head==tail时,说明队列为空
     *
     * @return 是否为空队列
     */
    @Override
    public boolean isEmpty() {
        return tail == front;
    }

    /**
     * 当tail只与head差一个节点时,队列已满
     *
     * @return 队列是否已满
     */
    public boolean isFull() {
        return (tail + 1) % data.length == front;
    }

    /**
     * 入队
     *
     * @param e 元素
     */
    @Override
    public void enqueue(E e) {
        if (isFull()) {
            //扩容为实际能承载的数据的2倍
            resize(getCapacity() * 2);
        }
        //入队,将尾节点先后移动一位即可.由于可能超出队列长度,故取队列长度的余数,让内存可以循环利用
        this.data[tail] = e;
        tail = (tail + 1) % data.length;
        size++;
    }

    /**
     * 出队
     *
     * @return 队首的值
     */
    @Override
    public E dequeue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Cannot dequeue from a empty queue.");
        }

        E ret = data[front];
        data[front] = null;
        size--;
        front++;

        //出队后可能不需要那么大空间了,此时进行缩容操作
        if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
            resize(getCapacity() / 2);
        }

        return ret;
    }

    /**
     * 扩容与缩容操作
     *
     * @param newCapacity 新容量
     */
    private void resize(int newCapacity) {
        //因为会浪费掉一个空间,实际容纳的数据量要-1
        E[] newData = (E[]) new Object[newCapacity + 1];
        //从0到size-1处即可
        for (int i = 0; i < size; i++) {
            //新数组数据是从0开始
            newData[i] = data[(i + front) % data.length];
        }

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

    @Override
    public E getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Cannot dequeue from a empty queue.");
        }
        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(",");
//            }
//        }

        //偏移量方式
        for (int i = 0; i < size; i++) {
            res.append(data[(i + front) % data.length]);
            if (i != size - 1) {
                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++) {
            queue.enqueue(i);
            System.out.println(queue);
            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值