• (三)循环队列


    目录


    前言

    循环队列,是为了解决 数组队列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; 
    ", 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; 
    ", 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();
        }
    }
    
    
  • 相关阅读:
    Android随笔使用ViewPager实现简单地图片的左右滑动切换
    简单地Android中图片的三级缓存机制
    Java中怎么遍历map中value值 (转载)
    创建可执行的jar包
    写出优秀的java代码(转载)
    [JS脚本]ajax status 错误定义
    jsonlib 之jsonConfig详细使用(转载写的不错)
    Driver not found Error when sonar server is just not running
    使用Maven生成manifest
    ECMAScript 面向对象技术
  • 原文地址:https://www.cnblogs.com/young-youth/p/11665675.html
Copyright © 2020-2023  润新知