(三)循环队列

目录


前言

循环队列,是为了解决 数组队列deQueue() 复杂度为 O(n) 的问题;

底层,我们不再使用之前我们实现的 动态数组 ,因为它的 reSize() 不符合我们的要求,对 循环队列 的扩容,要另写一套方法 ;


思想

循环队列,deQueue() 操作的时候,是不会将后面的元素,往前移动的,它使用一个标记 front 来记录当前队首在哪里,同样,由于后面的元素不会往前移动,那么数组的前端就会有空缺,当数组后面装满了的时候,就会再前面继续装 ;这里为了表示队尾在哪,用一个 tail 标记 ;

只有当数组的前后,都装满了,才会进行数组的扩容 ;

为了便于逻辑的编写,我们有意识的丢弃一个空间不用,这样当 front == tail 就表示队列为空,当 (tail + 1 ) % (数组长度-1) == front 就表示队列满了 ;


已实现方法

这里写图片描述


注意点

在进行 reSize() 操作的时候,我们要保证数组的最少长度为 3

因为,在我们的底层 toString() 方法中,有一个 bug :当满足数组长度为 2,队列中只有一个元素,然后执行出队列操作,就会触发这个 bug ;

toString() 代码(bug在for循环中)

@Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("LoopQueue : size = %d , capacity = %d ,front = %d ,tail = %d ,length = %d; \n", getSize(), getCapacity(), front, tail, array.length));
        res.append("Front [ ");

        for (int i = front; i != tail; i = (i + 1) % (array.length - 1)) {
            res.append(array[i]);
           if (!((i + 1) % (array.length - 1) == tail)) {
                res.append(", ");
           }
        }

        res.append(" ] Tail ");
        return res.toString();
    }

产生这个问题的原因很简单

呜呜呜,我debug了一上午,换来一个原因很简单。。

当满足上面触发 bug 条件的时候, front = 0 、tail = 1 array.length = 2 或者 front = 1 、tail = 0 array.length = 2;带入 for 循环,爽了,都是个死循环 ;

解决方法有2种

使用下面的方法遍历队列;或者在 reSize() 的时候,保证数组长度最小为 3

       for (int i = 0; i < size; i++) {
            res.append(array[(front + i) % (array.length - 1)]);
            if (i != size - 1) {
                res.append(", ");
            }
        }

关键点在于不能让 array.length - 1 == 1 ;不然循环队列,就无法循环了;

因此,稳妥点,还是保证数组的长度,比较靠谱;

 private void reSize(int newCapacity) {
//          保证了数组的长度,最少是 3
        E[] array = (E[]) new Object[newCapacity + 2];
//       复制原数组到新数组
        for (int i = 0; i < size; i++) {
            array[i] = this.array[(front + i) % (this.array.length - 1)];
        }
        this.array = array;
        front = 0;
        tail = size;
    }

java 代码


----------- Queue 接口 --------------

package xin.ijava.quene;

/**
 *   数组队列  、 循环队列
 *    时间比较 ;
 *    @author An
 */
@SuppressWarnings("unused")
public interface Queue<E> {

    /**
     * 获取队列中的实际元素个数
     * @return 元素个数
     */
    int getSize()  ;

    /**
     *   队列是否为空
     * @return  队列的情况
     */
    boolean isEmpty() ;

    /**
     * 获取当其队列的队首元素
     * @return 队首元素
     */
    E getFront();

    /**
     * 移除当前队首元素
     * @return  队首元素
     */
    E deQueue() ;

    /**
     * 将元素添加到 队尾
     * @param e 目标元素
     */
    void enQueue(E e) ;

}



------------- 循环队列实现类 ---------------

package xin.ijava.quene;

/**
 * 循环队列,解决 enQueue() 复杂度为 O(n) 的问题 ;
 * <p>
 * 底层实现不再是使用我们的动态数组;
 * 只是使用普通的数组,但是我们将要赋予它动态伸缩的能力 ;
 *
 * @author An
 */
public class LoopQueue<E> implements Queue<E> {

    /**
     * 底层维护的可怜弱小又无助的普通数组 ;
     */
    private E[] array;
    /**
     * 其实存储了多少个元素,可以根据 front 和 tail 算出来的 ;
     * 但是为了方便逻辑的编写,我们还是用一个变量记录下,等会挑战下,不记录的版本 ;
     */
    private int size;
    private int front, tail;

    public LoopQueue() {
        array = (E[]) new Object[10];
        size = 0;
        front = tail = 0;
    }

    /**
     * 真实开启内存的时候,比用户传进来的参数大一个 ,因为,我们要故意的抛弃一个内存不用
     *
     * @param capacity
     */
    public LoopQueue(int capacity) {
        array = (E[]) new Object[capacity + 1];
        size = 0;
        front = tail = 0;
    }

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

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public E getFront() {
        return array[front];
    }

    @Override
    public E deQueue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Queue is already empty ,can not execute deQueue method;");
        }
        /**
         *    移除队首元素,我们不再进行元素的移动,只是移动记录队首的标记
         *    移动的时候,注意,对数组的容量进行取余,防止越界 !
         */

        /**
         * 在进行缩容的时候,要保证数组大小大于等于3,因为在我们的toString 中,使用的遍历数组的方法有bug
         *
         *  array.length / 2 最小值是 1 ;
         */
        if (size == array.length / 4 && array.length / 2 != 0) {
            reSize(array.length / 2);
        }


        E temp = array[front];
        front = (front + 1) % (array.length - 1);
        size--;

        return temp;
    }

    @Override
    public void enQueue(E e) {
//        判断是否需要扩容
        if ((tail + 1) % (array.length - 1) == front) {
            reSize(2 * (array.length - 1));
        }
        array[tail] = e;
        tail = (tail + 1) % (array.length - 1);
        size++;
    }

    private void reSize(int newCapacity) {
//          保证了数组的长度,最少是 3
        E[] array = (E[]) new Object[newCapacity + 2];
//       复制原数组到新数组
        for (int i = 0; i < size; i++) {
            array[i] = this.array[(front + i) % (this.array.length - 1)];
        }
        this.array = array;
        front = 0;
        tail = size;
    }

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

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("LoopQueue : size = %d , capacity = %d ,front = %d ,tail = %d ,length = %d; \n", getSize(), getCapacity(), front, tail, array.length));
        res.append("Front [ ");

//        第二种方法 遍历队列,方法有 bug 的,
//        这样做,当数组长度为2的时候,第一个位置上有元素,然后执行出队列操作
//        然后打印队列的时候,下面的循环是死循环的,解决
//        解决方法有2种:使用下面的方法遍历队列;或者在reSize的时候,保证数组长度最小为3
        for (int i = front; i != tail; i = (i + 1) % (array.length - 1)) {
            res.append(array[i]);
            if (!((i + 1) % (array.length - 1) == tail)) {
                res.append(", ");
            }
        }

//        for (int i = 0; i < size; i++) {
//            res.append(array[(front + i) % (array.length - 1)]);
//            if (i != size - 1) {
//                res.append(", ");
//            }
//        }

        res.append(" ] Tail ");
        return res.toString();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值