• 用数组实现队列(顺序队列&循环队列)


    用数组实现队列(顺序队列&循环队列)

    顺序队列

    ↘️

    队列(先进先出)


    几个问题:

    • 队列方法:入队、出队
    • 队列的存储:即队首队尾两个指针,
    • 扩容:如果队列容量不够了,应该扩容,如果队尾没有位置了,队首有位置,应该把元素往前移

    主要是上面三个问题,在代码中都有体现,上面的扩容方法借鉴了ArrayList的扩容方法。

    package com.helius.structure.queue;
    
    import java.util.Arrays;
    
    /**
     * 用数组实现一个队列,即顺序队列
     */
    public class ArrayQueue {
        // 存储数据的数组
        private Object[] elements;
        //队列大小
        private int size;
        // 默认队列容量
        private int DEFAULT_CAPACITY = 10;
        // 队列头指针
        private int head;
        // 队列尾指针
        private int tail;
        
        private int MAX_ARRAY_SIZE  = Integer.MAX_VALUE-8;
    
        /**
         * 默认构造函数 初始化大小为10的队列
         */
        public ArrayQueue(){
            elements = new Object[DEFAULT_CAPACITY];
            initPointer(0,0);
        }
    
        /**
         * 通过传入的容量大小创建队列
         * @param capacity
         */
        public ArrayQueue(int capacity){
            elements = new Object[capacity];
            initPointer(0,0);
        }
    
        /**
         * 初始化队列头尾指针
         * @param head
         * @param tail
         */
        private void initPointer(int head,int tail){
            this.head = head;
            this.tail = tail;
        }
    
        /**
         * 元素入队列
         * @param element
         * @return
         */
        public boolean enqueue(Object element){
            ensureCapacityHelper();
            elements[tail++] = element;//在尾指针处存入元素且尾指针后移
            size++;//队列元素个数加1
            return true;
        }
    
        private void ensureCapacityHelper() {
            if(tail==elements.length){//尾指针已越过数组尾端
                //判断队列是否已满 即判断数组中是否还有可用存储空间
                //if(size<elements.length){
                if(head==0){
                    //扩容
                    grow(elements.length);
                }else{
                    //进行数据搬移操作 将数组中的数据依次向前挪动直至顶部
                    for(int i= head;i<tail;i++){
                        elements[i-head]=elements[i];
                    }
                    //数据搬移完后重新初始化头尾指针
                    initPointer(0,tail-head);
                }
            }
        }
        /**
         * 扩容
         * @param oldCapacity 原始容量
         */
        private void grow(int oldCapacity) {
            int newCapacity = oldCapacity+(oldCapacity>>1);
            if(newCapacity-oldCapacity<0){
                newCapacity = DEFAULT_CAPACITY;
            }
            if(newCapacity-MAX_ARRAY_SIZE>0){
                newCapacity = hugeCapacity(newCapacity);
            }
            elements = Arrays.copyOf(elements,newCapacity);
        }
        private int hugeCapacity(int newCapacity) {
            return (newCapacity>MAX_ARRAY_SIZE)? Integer.MAX_VALUE:newCapacity;
        }
    
        /**
         * 出队列
         * @return
         */
        public Object dequeue(){
            if(head==tail){
                return null;//队列中没有数据
            }
            Object obj=elements[head++];//取出队列头的元素且头指针后移
            size--;//队列中元素个数减1
            return obj;
        }
    
        /**
         * 获取队列元素个数
         * @return
         */
        public int getSize() {
            return size;
        }
    }
    
    

    测试用例

    public class TestArrayQueue {
    
        public static void main(String[] args) {
            ArrayQueue queue = new ArrayQueue(4);
            //入队列
            queue.enqueue("helius1");
            queue.enqueue("helius2");
            queue.enqueue("helius3");
            queue.enqueue("helius4");
            //此时入队列应该走扩容的逻辑
            queue.enqueue("helius5");
            queue.enqueue("helius6");
            //出队列
            System.out.println(queue.dequeue());
            System.out.println(queue.dequeue());
            //此时入队列应该走数据搬移逻辑
            queue.enqueue("helius7");
            //出队列
            System.out.println(queue.dequeue());
            //入队列
            queue.enqueue("helius8");
            //出队列
            System.out.println(queue.dequeue());
            System.out.println(queue.dequeue());
            System.out.println(queue.dequeue());
            System.out.println(queue.dequeue());
            System.out.println(queue.dequeue());
            System.out.println(queue.dequeue());
            //入队列
            queue.enqueue("helius9");
            queue.enqueue("helius10");
            queue.enqueue("helius11");
            queue.enqueue("helius12");
            //出队列
            System.out.println(queue.dequeue());
            System.out.println(queue.dequeue());
        }
    }
    

    结果:

    helius1
    helius2
    helius3
    helius4
    helius5
    helius6
    helius7
    helius8
    null
    helius9
    helius10
    

    循环队列

    用java实现循环队列的方法:

    1. 增加一个属性size用来记录目前的元素个数。目的是当head=rear的时候,通过size=0还是size=数组长度,来区分队列为空,或者队列已满。

    2. 数组中只存储数组大小-1个元素,保证rear转一圈之后不会和head相等,也就是队列满的时候,rear+1=head,中间刚好空一个元素。

      当rear=head的时候,一定是队列空了。

    队列(Queue)两端允许操作的类型不一样:

    可以进行删除的一端称为队头,这种操作也叫出队dequeue;

    可以进行插入的一端称为队尾,这种操作也叫入队enqueue。

    队列的示意图

    img

    实现队列时,要注意的是假溢出现象,如上图的最后一幅图。

    如图所示的假溢出现象,顺序队列可以如此,循环队列我们可以让这个尾指针指向front前面的元素,这也正符合我们想要的循环队列的定义。

    img

    解决办法:使用链式存储,这显然可以。在顺序存储时,我们常见的解决办法是把它首尾相接,构成循环队列,这可以充分利用队列的存储空间。

    循环队列示意图:

    img

    在上图中,front指向队列中第一个元素,rear指向队列队尾的下一个位置。

    但依然存在一个问题:当front和rear指向同一个位置时,这代表的是队空还是队满呢?大家可以想象下这种情景。

    解决这种问题的常见做法是这样的:

    使用一标记,用以区分这种易混淆的情形。

    牺牲一个元素空间。当front和rear相等时,为空;当rear的下一个位置是front时,为满。

    如下图:

    imgimg

    下面我们给出循环队列,并采用第二种方式,即牺牲一个元素空间来区分队空和队满的代码.

    几个重点:

    1、front指向队头,rear指向队尾的下一个位置。

    2、队为空的判断:frontrear;队为满的判断:(rear+1)%MAXSIZEfront。

    上面说的rear即为代码中的的tail

    /**
     * 使用数组实现循环队列
     * @author Helius
     */
    public class CirculiQueue {
        //存储队列数据的数组
        private Object[] elements;
        //默认数组容量
        private int DEFAULT_CAPACITY=10;
        //队列中元素个数
        private int size;
        // 队列头指针
        private int head;
        //队列尾指针
        private int tail;
    
        /**
         * 默认构造函数
         */
        public CirculiQueue(){
            elements = new Object[DEFAULT_CAPACITY];
        }
    
        /**
         * 通过传入的容量参数构造队列
         * @param capacity
         */
        public CirculiQueue(int capacity){
            elements = new Object[capacity];
        }
    
        /**
         * 元素入队列
         * @param element
         * @return
         */
        public boolean enqueue(Object element){
            //判断队列是否已满
            if(head == (tail+1)%elements.length){
                //队列已满
                return false;
            }
            //将元素存入tail位置上
            elements[tail]=element;
            //尾指针后移
            /*tail++;
            if(tail==elements.length){
                tail = 0;
            }*/
            tail = (tail+1)%elements.length;
            size++;
            return true;
        }
    
        /**
         * 元素出队列
         * @return
         */
        public Object dequeue(){
            //判断队列是否为空
            if(head==tail){
                return null;
            }
            //获取head位置上的元素
            Object element = elements[head];
            //头指针后移
            /*head++;
            if(head==elements.length){
                head = 0;
            }*/
            head = (head+1)%elements.length;
            size--;
            return element;
        }
    
        /**
         * 获取队列大小
         * @return
         */
        public int getSize() {
            return size;
        }
    }
    

    这里也添加了size属性,可以稍做修改,来实现第一种方式。

  • 相关阅读:
    Python3 -- 多线程(threading模块、queue模块)
    MySQL -- 常用汇总
    MySQL -- 常用函数汇总
    MySQL -- 数据表添加字段(三种方式)
    MySQL -- 查看表结构命令
    MySQL -- 修改/删除字段
    MySQL -- ALTER TABLE:修改数据表
    MySQL -- 单行注释和多行注释
    GCC 提供的原子操作
    内存区划分、内存分配、常量存储区、堆、栈、自由存储区、全局区[C++][内存管理][转载]
  • 原文地址:https://www.cnblogs.com/heliusKing/p/12326816.html
Copyright © 2020-2023  润新知