• Java中队列


    定义

    队的操作是在两端进行,一端只能进行插入操作(入队),称为队尾,一端只能进行删除操作(出队),称为队尾。

    队列的运算规则是FIFO(first in first out)。

    队列的入队、出队操作分别具有入队和出队的指针,通常以f(front) 表示队首指针,r(rear)表示队尾指针。

    队列的存储具有顺序存储和链式存储两种方式。

    基本运算:

    • 初始化;
    • 判断空;
    • 判断满;
    • 入队;
    • 出队;
    • 取出队首元素。
    package com.wuwii.test;
    
    /**
     * 队列
     * @author Zhang Kai
     * @version 1.0
     * @since <pre>2017/12/17 13:12</pre>
     */
    public interface Queue<E> {
        /**
         * 入队
         * @param element 入队元素
         */
        void enqueue(E element);
    
        /**
         * 出队第一个元素
         * @return 第一个元素
         */
        E dequeue();
    
        /**
         * 返回队首的元素
         * @return E 队首元素
         */
        E frontValue();
    
        /**
         * 清空队列的所有元素
         */
        void clear();
    
        /**
         * 判断队列是否为空
         * @return true 为空,
         */
        Boolean isEmpty();
    
    }
    
    

    顺序队列

    。由于顺序存储结构的空间是连续的,需要一个向量空间存储当前队列的所有元素,由于队列的对头和队尾的位置是变化的。初始化的时候,rear 和front 都设置为 0 ;入队的时候将新元素插入rear 所指的位置,然后将rear 加 1 ;出队的时候,删除 front 所指的元素,然后将 front 加 1并返回该元素。由此可见,当头尾指针相等时队列为空。在非空队列中,头指针始终指向队头元素,而尾指针始终指向队尾元素的下个位置。

    这里其实存在一个问题需要解决。队列的顺序存储结构同样也存在上溢和下溢的问题。而且,队列中还存在假溢出现象,指的是队列在入队和出队的操作中,头尾指针不断增加而不减少或者只减少而不增加,导致被删除的元素的空间无法重新利用,因此,尽管队列中实际的元素个数远远小于向量空间的规模,但也可能由于尾指针已远远超越向量空间的上界而不能进行入队或出队的操作,这种现象称为假上溢。

    解决假上溢有多种方法。

    • 固定头指针:固定队首指针,一旦删除元素,需要移动所有元素,然后修改队尾指针,这样就又可以插入元素,只有在不能插入元素的时候,队列才是满的,否则可以一直插入元素,但是也存在一个,每次删除元素的时候,都移动了大量的数据,造成性能损失;
    • 循环向量:存储在循环向量的队列成为循环队列。

    循环队列

    存储在循环向量的队列成为循环队列
    image

    假设向量的空间为m,只要在入队或者出队操作的时候,将队首和队尾的指针对m做求模运算即可实现队首和队尾指针的循环,因此,队首和队尾指针的数值取值范围为0 ~ m-1 之间。

    入队时:rear = (rear + 1) % maxSize;
    出队时:front = (front + 1) % maxSize;

    入队时,rear指针不断加一,知道rear 指针追上 front 指针的时候,队列满,rear = front,出队的时候,只需要判断 front = rear。因此,又出现了一个问题,判断队列的空与满的条件相同。

    区分队列的空与满:

    • 方式一:浪费一个空间;rear指向刚刚插入的位置,front指向刚刚插入的位置;入队的时候先不修改rear 指针,先去判断(rear + 1) % maxSize = front,成立,表示队列已经满了。出队的时候只需要判断 front = rear,不为空的话,删除front = (front + 1) % maxSize位置的元素。
    • 方式二:设定一个变量来保存存储的元素个数;将该变量跟队列最大容量进行比较,如果相等,则队列满,如果该变量为0,表示队列为空。

    上面两种方式都需要消费一个存储单元。

    实现循环队列需要两个指针的开销,需要定义数组的最大下标范围,遵循先进先出原则。

    package com.wuwii.test;
    
    /**
     * 顺序队列 -》循环队列
     * @author Zhang Kai
     * @version 1.0
     * @since <pre>2017/12/17 13:17</pre>
     */
    public class Squeue<E> implements Queue<E> {
        /**
         * 队列默认大小
         */
        private final int defaultSize = 10;
        /**
         * 队列存储的元素个数
         */
        private int size;
        /**
         * 队首位置
         */
        private int front;
        /**
         * 队尾位置
         */
        private int rear;
        /**
         * 存储数组
         */
        private Object[] elements;
    
        /**
         * 无参构造,使用默认大小10
         */
        public Squeue() {
            initQueue(defaultSize);
        }
    
        /**
         * 有参构造,指定队列大小
         * @param givenSize 需要指定队列大小
         */
        public Squeue(int givenSize) {
            initQueue(givenSize);
        }
        /**
         * 入队
         *
         * @param element 入队元素
         */
        @Override
        public void enqueue(E element) {
            checkFull();
            rear = (rear + 1) % size;
            elements[rear] = element;
        }
    
        /**
         * 出队第一个元素
         *
         * @return 第一个元素
         */
        @Override
        public E dequeue() {
            checkEmpty();
            front = (front + 1) % size;
            return (E) elements[front];
        }
    
        /**
         * 返回队首的元素
         *
         * @return E 队首元素
         */
        @Override
        public E frontValue() {
            return (E) elements[(front + 1) % size];
        }
    
        /**
         * 清空队列的所有元素
         */
        @Override
        public void clear() {
            if (isEmpty()) {
                return;
            }
            front = rear = 0;
        }
    
        /**
         * 判断队列是否为空
         *
         * @return true 为空,
         */
        @Override
        public Boolean isEmpty() {
            return front == rear;
        }
    
        /**
         * 初始化队列
         * @param givenSize 给定大小
         */
        private void initQueue(int givenSize) {
            size = givenSize + 1;
            front = rear = 0;
            elements = new Object[size];
        }
    
        /**
         * 检查队列满
         * 队列满了抛出异常
         */
        private void checkFull() {
            if (front == (rear + 1) % size) {
                throw new RuntimeException("Queue is full");
            }
        }
    
        /**
         * 检查队列为空
         * 为空抛出异常
         */
        private void checkEmpty() {
            if (isEmpty()) {
                throw new RuntimeException("Queue is empty");
            }
        }
    }
    
    

    链式队列

    相比顺序队列,链式队列的队首和队尾改为指针。

    链式队列存在一个很方便的特点,只需要一个队尾指针就可以完成队列所有的基本运算,主要原因就是队列的运算方式时先进先出,已知一个任意节点,就可以查找出所有,所以只需要一个队尾就可以了,节约空间开销。

    清空队列只需要将指针删除即可。

    package com.wuwii.test;
    
    /**
     * 链式队列,循环队列
     * @author Zhang Kai
     * @version 1.0
     * @since <pre>2017/12/17 14:14</pre>
     */
    public class LinkQueue<E> implements Queue<E> {
        /**
         * 队首指针
         */
        private Node<E> front;
        /**
         * 队尾指针
         */
        private Node<E> rear;
    
        public LinkQueue() {
            initQueue();
        }
    
        /**
         * 入队
         *
         * @param element 入队元素
         */
        @Override
        public void enqueue(E element) {
            if (rear == null) {
                rear = new Node<>(null, element);
            } else {
                front = rear = new Node<>(null, element);
            }
        }
    
        /**
         * 出队第一个元素
         *
         * @return 第一个元素
         */
        @Override
        public E dequeue() {
            checkEmpty();
            E e = front.element;
            front = front.next;
            return e;
        }
    
        /**
         * 返回队首的元素
         *
         * @return E 队首元素
         */
        @Override
        public E frontValue() {
            return front.element;
        }
    
        /**
         * 清空队列的所有元素
         */
        @Override
        public void clear() {
            front = rear = null;
        }
    
        /**
         * 判断队列是否为空
         *
         * @return true 为空,
         */
        @Override
        public Boolean isEmpty() {
            return rear == null;
        }
    
        /**
         * 存储单元,链表单元
         * @param <E>
         */
        private static class Node<E> {
            Node<E> next;
            E element;
            Node(Node<E> next, E element) {
                this.next = next;
                this.element = element;
            }
        }
    
        /**
         * 初始化队列
         */
        private void initQueue() {
            front = rear = null;
        }
        /**
         * 检查队列为空
         * 为空抛出异常
         */
        private void checkEmpty() {
            if (isEmpty()) {
                throw new RuntimeException("Queue is empty");
            }
        }
    }
    
    
  • 相关阅读:
    springboot 和 spring clould 的版本匹配问题
    行到水穷处,坐看云起时!
    转: 从单体应用 -> SOA--> 微服务 ,服务治理 [熔断,限流,降级,服务恢复,服务容错,监控等等]---> RPC ---> 下一代技术[Service Mesh]
    spring-boot自定义线程池
    千与千寻的内容抓手
    哲学三问
    简约的人生
    关于中间件整理
    此心光明,亦复何言!
    能容的下你身边比你优秀的人---是一种修行
  • 原文地址:https://www.cnblogs.com/qnight/p/8983142.html
Copyright © 2020-2023  润新知