吹牛必备之循环队列与双端循环队列

循环队列

循环队列概念

所谓的循环队列,其实还是一个队列,从外部调用的接口和实际的功能上看和普通的队列完全没有区别,而之所以它叫循环队列而不是队列,是因为它用于实现的底层逻辑和一般队列不同,一般队列底层采用双向链表,而循环队列底层采用索引不断循环的数组实现。

循环队列操作图示

建立循环队列,势必要先建立一个数组:

E elements=(E[]) new Object[8];//其中E表示泛型

假如向队列中入队11,22,33,44这四个元素,则其存储结构如图所示。

在这里插入图片描述
此时若将元素55入队列,则直接在尾元素后入队列:

在这里插入图片描述
倘若要出队列,则稍稍复杂一些,将队头元素清空,并将front的指向后移:
在这里插入图片描述
假设在此基础上出队3次,并入队66,77,88,则结果如下图所示:
在这里插入图片描述
若在数组没有更大的索引位置可以使用时,还要继续入队99,则采用索引循环的方法,即放到左边空余的数组空间。此时入队,若元素从0开始放置(即front从0开始),入队元素应该放置在索引为4即size的位置(因为已有4个元素,分别放置了0,1,2,3的位置),而此时由于front的偏移,入队元素放置的位置也需要进行偏移,即size+front=8。而8明显超出了数组可以用的索引,此时将8对数组大小取模,放置左侧空余的数组空间(循环)。
在这里插入图片描述
再次入队100,101,102:

在这里插入图片描述
此时队列已经装满,若还要继续入队,则需要进行扩容(新建一个更大的数组,将数组从0开始迁移过去,并修改原数组引用对象指向新数组):

private void ensureCapacity(int newSize){
    int oldCapacity=elements.length;
    //如果容量可以承受则不扩容
    if(newSize<=oldCapacity)return;
    //否则扩容1.5
    newSize=oldCapacity+(oldCapacity>>1);
    E[] newElements= (E[]) new Object[newSize];
    //数据迁移
    for(int i=0;i<size;i++)
        newElements[i]=elements[index(i)];
    elements=newElements;
    front=0;//指针归零
    System.out.println(oldCapacity+"扩容为"+newSize);
}

/**
* 简化取模的索引映射
 * @param index
 * @return
 */
private int index(int index){
    //避免取模运算(效率低下)
    index+=front;
    return index-(index>=elements.length?elements.length:0);
}

此时扩容后的结果将为:

在这里插入图片描述
然后再入队:
在这里插入图片描述
以上就是循环队列的操作步骤。

具体实现

public class CircleQueue<E>{
    private int front;//指向头节点
    private int size;
    private E[] elements;
    //默认队列大小
    private static final int DEFAULT_CAPACITY=10;

    public  CircleQueue(){
        this(DEFAULT_CAPACITY);
    }

    public  CircleQueue(int capacity){
        capacity=capacity>DEFAULT_CAPACITY?capacity:DEFAULT_CAPACITY;
        elements= (E[]) new Object[capacity];
    }

    /**
     * 获取队列大小
     * @return
     */
    public int size(){
        return size;
    }

    /**
     * 判断数组是否为空
     * @return
     */
    public boolean isEmpty(){
        return size==0;
    }

    /**
     * 清空队列
     */
    public void clear(){
        //清空所有引用,等待jvm进行垃圾回收
        for(int i=0;i<size;i++)
            elements[index(i)]=null;
        size=0;
        //还原指针
        front=0;
    }


    /**
     * 入队
     */
    // 0 0 1 2 3
    public void offer(E element){
        ensureCapacity(size+1);
        elements[index(size)]=element;//末尾入队
        size++;
    }

    private void ensureCapacity(int newSize){
        int oldCapacity=elements.length;
        //如果容量可以承受
        if(newSize<=oldCapacity)return;
        //否则扩容1.5
        newSize=oldCapacity+(oldCapacity>>1);
        E[] newElements= (E[]) new Object[newSize];
        //数据迁移
        for(int i=0;i<size;i++)
            newElements[i]=elements[index(i)];
        elements=newElements;
        front=0;//指针归零
        System.out.println(oldCapacity+"扩容为"+newSize);
    }

    /**
     * 简化取模的索引映射
     * @param index
     * @return
     */
    private int index(int index){
        //避免取模运算(效率低下)
        index+=front;
        return index-(index>=elements.length?elements.length:0);
    }
    /**
     * 出队
     * @return
     */
    public E poll(){
        E frontElement=elements[front];
        elements[front]=null;
        front=index(1);//更新指针到下一个元素
        size--;
        return frontElement;
    }

    /**
     * 获取队头元素
     * @return
     */
    public E peek(){
        return elements[front];
    }

    @Override
    public String toString() {
        return "CircleQueue{" +
                "front=" + front +
                ", size=" + size +
                ", elements=" + Arrays.toString(elements) +
                '}';
    }
}

循环双端队列

循环双端队列概念

循环双端队列其实就是一个双端队列,即可以在队头和队尾都进行入队或者出队操作的队列。它与循环队列相比,也只是增加了在队尾进行出队操作的接口offerRear和在队头进行入队操作的pollFront,其次由于在队头入队需要获取到原始索引为-1的映射索引(即头节点之前的节点,头节点原始索引为0),还需要对提供索引映射的index(int index)方法进行修改。

具体实现

public class CircleDeque<E> {
    private int front;//指向头节点
    private int size;
    private E[] elements;
    //默认队列大小
    private static final int DEFAULT_CAPACITY=10;

    public  CircleDeque(){
        this(DEFAULT_CAPACITY);
    }

    public  CircleDeque(int capacity){
        capacity=capacity>DEFAULT_CAPACITY?capacity:DEFAULT_CAPACITY;
        elements= (E[]) new Object[capacity];
    }

    /**
     * 获取队列大小
     * @return
     */
    public int size(){
        return size;
    }

    /**
     * 判断数组是否为空
     * @return
     */
    public boolean isEmpty(){
        return size==0;
    }

    /**
     * 清空队列
     */
    public void clear(){
        //清空所有引用,等待jvm进行垃圾回收
        for(int i=0;i<size;i++)
            elements[index(i)]=null;
        size=0;
        //还原指针
        front=0;
    }


    /**
     * 从队尾入队
     */
    // 0 0 1 2 3
    public void offerRear(E element){
        ensureCapacity(size+1);
        elements[index(size)]=element;//末尾入队
        size++;
    }

    /**
     * 从队头入队
     */
    public void offerFront(E element){
        ensureCapacity(size+1);
        int newFront=size==0?0:index(-1);//若放放入的是第一个元素,直接放在0处
        elements[front=newFront]=element;//队头入队并跟新front
        size++;
    }

    private void ensureCapacity(int newSize){
        int oldCapacity=elements.length;
        //如果容量可以承受
        if(newSize<=oldCapacity)return;
        //否则扩容1.5
        newSize=oldCapacity+(oldCapacity>>1);
        E[] newElements= (E[]) new Object[newSize];
        //数据迁移
        for(int i=0;i<size;i++)
            newElements[i]=elements[index(i)];
        elements=newElements;
        front=0;//指针归零
        System.out.println(oldCapacity+"扩容为"+newSize);
    }

    /**
     * 简化取模的索引映射
     * @param index
     * @return
     */
    private int index(int index){
        //防止index为-1
        //避免取模操作
        index+=front;
        if(index<0)
            return index+elements.length;
        return index-(index>=elements.length?elements.length:0);
    }
    /**
     * 从队头出队
     * @return
     */
    public E pollFront(){
        E frontElement=elements[front];
        elements[front]=null;
        front=index(1);//更新指针到下一个元素
        size--;
        return frontElement;
    }

    /**
     * 从队尾出队
     * @return
     */
    public E pollRear(){
        int rear=index(size-1);
        E frontElement=elements[rear];
        elements[rear]=null;//尾部出队
        size--;
        return frontElement;
    }

    /**
     * 获取队头元素
     * @return
     */
    public E peekFront(){
        return elements[front];
    }

    /**
     * 获取队尾元素
     * @return
     */
    public E peekRear(){
        return elements[index(size-1)];
    }

    @Override
    public String toString() {
        return "CircleDeque{" +
                "front=" + front +
                ", size=" + size +
                ", elements=" + Arrays.toString(elements) +
                '}';
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值