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

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

顺序队列

↘️

队列(先进先出)


几个问题:

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

主要是上面三个问题,在代码中都有体现,上面的扩容方法借鉴了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属性,可以稍做修改,来实现第一种方式。

posted @ 2020-02-18 16:39  HeliusKing  阅读(4250)  评论(0编辑  收藏  举报