先进先出的排队结构——队列(Queue)

设计背景

“后进先出”(LIFO)的特性相对应,应当还有一种“先进先出”(FIFO)特性的数据结构,这种数据结构被称之为队列(Queue)。该结构的实现过程与栈几乎类似,区别在于每次存入元素和获取元素的位置不同。
在这里插入图片描述


结构分析

【结构类型】线性结构
【底层实现】动态数组(ArrayList)
【核心方法】
public void enqueue(E e); //入队
public E dequeue(); //出队
public E getFront(); //获取队首的元素


代码实现

1. 利用ArrayList实现普通队列
public class ArrayQueue<E> implements Queue<E> {
    /**
     * 实例域:动态数组
     */
    private ArrayList<E> array;

    /**
     * 带参构造器:对实例域进行初始化
     * @param capacity 数组容量
     */
    public ArrayQueue(int capacity) {
        array = new ArrayList<>(capacity);
    }

    /**
     * 无参构造器:利用默认值对实例域进行初始化
     */
    public ArrayQueue() {
        array = new ArrayList<>();
    }

    @Override
    public void enqueue(E e) {
        array.addLast(e);
    }

    @Override
    public E dequeue() {
        return array.removeFirst();
    }

    @Override
    public E getFront() {
        return array.get(0);
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }

    @Override
    public int getSize() {
        return array.getSize();
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Queue: ");
        res.append("front [");
        for(int i = 0 ; i < array.getSize() ; i ++){
            res.append(array.get(i));
            if(i != array.getSize() - 1)
                res.append(", ");
        }
        res.append("] tail");
        return res.toString();
    }

}

改进策略

【改进思想】普通的数组队列(ArrayQueue)进行出队操作时,需要将后方的每一个元素向前移动(时间复杂度为O(n)级别),这导致普通队列的性能较低。我们期望更改队列设计,以达到出队操作不需要对后方元素进行遍历,并且使得出队操作完成后所闲置的空间能被循环利用。根据这种思想所改良的队列就称之为循环队列(LoopQueue)。
在这里插入图片描述
【改进方法】因为循环队列(LoopQueue)具有“空间再利用”的结构性,故底层无法直接使用原生的动态数组(ArrayList)实现,而需要在ArrayList的基础上进行重新改写。
设计队列的首/尾索引front和tail,初始化时预留一个空间长度,令front == tail == 0,此时队列为空
当(tail + 1) % data.length == front时,队列为满,触发扩容操作。
缩容操作的触发条件与ArrayList类似,但要注意将预留空间除外。


代码实现

2. 利用Array实现循环队列
public class LoopQueue<E> implements Queue<E> {
    /**
     * 实例域:泛型数组、首/尾索引、元素个数
     */
    private E[] data;
    private int front;
    private int tail;
    private int size;

    /**
     * 带参构造器:初始化实例域
     * @param capacity 数组容量
     */
    public LoopQueue(int capacity) {
        data = (E[]) new Object[capacity + 1];  // 注:循环队列需预存一个闲置空间
        front = 0;
        tail = 0;
        size = 0;
    }

    /**
     * 无参构造器:用默认值初始化实例域
     */
    public LoopQueue() {
        this(20);
    }

    /**
     * 方法:获取数组容量
     * @return
     */
    public int getCapacity() {
        return data.length - 1;  // 注:循环队列预存了一个闲置空间
    }

    @Override
    public void enqueue(E e) {
        // 当数组已满时进行扩容操作,新队列大小为原来的2倍
        if ((tail + 1) % data.length == front) {
            resize(getCapacity() * 2);
        }
        // 将元素插入队尾
        data[tail] = e;
        // 尾索引后移一位
        tail = (tail + 1) % data.length;
        // 元素个数加一
        size++;
    }

    @Override
    public E dequeue() {
        // 判断队列是否为空
        if (isEmpty()) {
            throw new IllegalArgumentException("Queue is empty!");
        }
        // 将队首元素存入临时变量
        E ret = data[front];
        // 移除队首元素
        data[front] = null;
        // 首索引后移一位
        front = (front + 1) % data.length;
        // 元素个数减一
        size--;
        // 当数组3/4的空间为空时,进行缩容操作,新队列大小为原来的一半
        if (size == getCapacity() / 4 && size / 2 != 0) {
            resize(getCapacity() / 2);
        }
        // 返回临时变量
        return ret;
    }

    /**
     * 方法:对队列的容量进行调整
     * @param capacity 新队列容量
     */
    private void resize(int capacity) {
        // 新建一个数组
        E[] newData = (E[]) new Object[capacity];
        // 将旧数组的数据拷贝至新数组
        for (int i = 0; i < size; i++) {
            newData[i] = data[(i + front) % data.length];
        }
        // 将数组变量引用至新数组
        data = newData;
        // 维护首/尾索引
        front = 0;
        tail = size;
    }

    @Override
    public E getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Queue is empty!");
        }
        return data[front];
    }

    @Override
    public boolean isEmpty() {
        return front == tail;  // 使用size == 0亦可
    }

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

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity()));
        res.append("front [");
        for(int i = front ; i != tail ; i = (i + 1) % data.length){
            res.append(data[i]);
            if((i + 1) % data.length != tail)
                res.append(", ");
        }
        res.append("] tail");
        return res.toString();
    }

}
3. 利用LinkedList实现循环队列
public class LinkedListQueue<E> implements Queue<E> {
    /**
     * 内部类:节点
     */
    private class Node {
        /**
         * 实例域:节点元素、节点引用
         */
        public E e;
        public Node next;

        /**
         * 构造器:对实例域进行初始化
         * @param e 元素
         * @param next 引用指向
         */
        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        public Node(E e) {
            this(e, null);
        }

        public Node() {
            this(null, null);
        }
    }

    /**
     * 实例域:首/尾指针、元素个数
     */
    private Node head, tail;
    private int size;

    /**
     * 无参构造器:用默认值对变量进行初始化
     */
    public LinkedListQueue() {
        head = null;
        tail = null;
        size = 0;
    }

    @Override
    public void enqueue(E e) {
        // 判断链表是否为空,若为空则在尾指针处新建节点
        if (tail == null) {
            tail = new Node(e);
            head = tail;
        } else {
            tail.next = new Node(e);
            tail = tail.next;
        }
        // 元素个数加一
        size++;
    }

    @Override
    public E dequeue() {
        // 判断队列是否为空
        if (isEmpty()) {
            throw new IllegalArgumentException("Queue is empty!");
        }
        // 将待删除的节点存入临时变量
        Node delNode = head;
        // 头指针后移
        head = head.next;
        // 将待删除节点指向空
        delNode.next = null;
        // 判断数组是否为空,若为空则将尾指针也赋为空
        if (head == null) {
            tail = null;
        }
        // 元素个数减一
        size--;
        // 返回临时变量所存储的元素
        return delNode.e;
    }

    @Override
    public E getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Queue is empty!");
        }
        return head.e;
    }

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

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

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Queue: front ");
        Node cur = head;
        while(cur != null) {
            res.append(cur.e + "->");
            cur = cur.next;
        }
        res.append("NULL tail");
        return res.toString();
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值