zoukankan      html  css  js  c++  java
  • 队列

    队列

    队列(Queue)

    队列一种特殊的线性表,只能在头尾两端进行操作

    • 队尾(rear):只能从队尾添加元素,一般叫做enQueue,入队
    • 队头(front):只能从队头移除元素,一般叫做deQueue,出队
    • 先进先出的原则,First In First Out,FIFO

    队列(Queue)接口设计

    int size();//元素的数量
    boolean isEmpty();//是否为空
    void clear();//清空
    void enQueue(Eelement);//入队
    E deQueue();//出队
    E front();//获取队列的头元素
    

    队列的内部实现可以使用 动态数组、链表 数据结构

    优先使用双向链表,因为队列主要是往头尾操作元素

    队列的实现
    /**
     * 队列的实现 --- 使用循环链表实现
     * @param <E>
     */
    public class Queue<E> {
        //
        private List<E> list = new LinkedList<>();
        /**
         * 元素的数量
         * @return
         */
        public int size(){
            // 队列中元素数量, 就是列表中存储的元素数量
            return list.size();
        }
        /**
         * 是否为空
         * @return
         */
        public boolean isEmpty(){
            // 队列是否空, 就是列表是否空
            return list.isEmpty();
        }
    
        /**
         * 清空队列元素
         */
        public void clear(){
            // 清空队列, 就是清空列表
            list.clear();
        }
    
        /**
         * 入队
         * @param element
         */
        public void enQueue(E element) {
            //队头 链表尾部添加元素
            list.add(element);
        }
    
        /**
         * 出队
         * @return
         */
        public E deQueue() {
            //队尾 链表0结点删除
            return list.remove(0);
        }
    
        /**
         * 获取队列的头元素
         * @return
         */
        public E front() {
            //获取链表第0个结点
            return list.get(0);
        }
    }
    

    双端队列(deque )

    双端队列是能在头尾两端添加删除的队列

    deque - double ended queue

    双端队列接口设计
    int size();//元素的数量
    boolean isEmpty();//是否为空
    void clear();//清空
    void enQueueRear(E element);//从队尾入队
    E deQueueFront();//从队头出队
    void enQueueFront(E element);//从队头入队
    E deQueueRear();//从队尾出队
    E front();//获取队列的头元素
    E rear();//获取队列的尾元素
    
    双端队列的实现
    /**
     * 双端队列的实现 --- 使用循环链表实现
     * @param <E>
     */
    public class Deque<E> {
        //使用
        private List<E> list = new LinkedList<>();
        /**
         * 元素的数量
         * @return
         */
        public int size(){
            // 队列中元素数量, 就是列表中存储的元素数量
            return list.size();
        }
        /**
         * 是否为空
         * @return
         */
        public boolean isEmpty(){
            // 队列是否空, 就是列表是否空
            return list.isEmpty();
        }
    
        /**
         * 清空双端队列元素
         */
        public void clear(){
            // 清空双端队列, 就是清空列表
            list.clear();
        }
    
        /**
         * 从队尾入队
         * @param element
         */
        void enQueueRear(E element){
            //链表尾部添加元素
            list.add(element);
        }
    
        /**
         * 从队头出队
         * @return
         */
        E deQueueFront(){
            //链表0 结点删除
            return list.remove(0);
        }
    
        /**
         * 从队头入队
         * @param element
         */
        void enQueueFront(E element){
            //往链表0处 添加
            list.add(0,element);
        }
    
        /**
         * 从队尾出队
         * @return
         */
        E deQueueRear(){
            // 删除 size - 1 个
            return list.remove(list.size() - 1);
        }
    
        /**
         * 获取队列的头元素
         * @return
         */
        E front(){
            //获取链表第0个结点
            return list.get(0);
        }
    
        /**
         * 获取队列的尾元素
         * @return
         */
        E rear(){
            //获取链表第size - 1个结点
            return list.get(list.size() - 1);
        }
    }
    

    循环队列(Circle Queue)

    实现队列底层也可以使用动态数组实现,并且各项接口也可以优化到O(1)的时间复杂度

    这个用数组实现的并且优化之后的队列也叫做:循环队列

    • 使用一个索引变量front控制第0个元素所在位置
    • 每一次出队,就将front位置的元素取出并删除,然后front向后+1
    • 每一次入队,都根据front和当前元素数量计算出入栈元素应该存入的索引,然后将元素存入到数组对应索引的位置上

    循环双端队列:可以进行两端添加、删除操作的循环队列

    循环队列接口设计
    int front;// 记录第0个元素的索引
    int size;// 当前队列存储的元素个数
    E[] elements;// 用来存储元素的数组
    int size();// 当前队列存储的元素数量
    boolean isEmpty();// 当前队列是否为空
    void enQueue(E element);// 入队
    E deQueue();// 出队
    E front();// 查看索引为0的元素
    
    循环队列的实现
    构造函数

    如果构建的数组空间小于默认空间,则会以默认空间构建数组

    public class CircleQueue<E> {
        /**
         * 记录第0个元素的索引 即队头
         */
        private int front;
        /**
         * 当前队列存储的元素个数
         */
        private int size;
        /**
         * 用来存储元素的数组
         */
        private E[] elements;
        /**
         * 默认数组的容量
         */
        private static final int DEFAULT_CAPACITY = 10;
        /**
         * 默认构造函数
         */
        public CircleQueue() {
            this.elements = (E[]) new Object[DEFAULT_CAPACITY];
        }
        
        /**
         * 当前队列存储的元素数量
         * @return
         */
        public int size(){
            return size;
        }
    
        /**
         * 当前队列是否为空
         * @return
         */
        public boolean isEmpty(){
            return size == 0;
        }
        
        /**
         * 查看索引为0的元素 即获取队头元素
         * @return
         */
        E front(){
            return elements[front];
        }
    }
    
    入队

    需要计算入队的实际索引

    public class CircleQueue<E> {
        
        ......
        
        /**
         * 入队
         * @param element
         */
        void enQueue(E element){       
            //队尾
            elements[(front + size) % elements.length] = element;
            //size加 1
            size ++;
        }
        
        ......
        
    }
    
    出队

    出队后需要更新front

    public class CircleQueue<E> {
        
        ......
        
        /**
         * 出队
         * @return
         */
        E deQueue(){
            //获取出队元素
            E frontElement = elements[front];
            //将对头置为空
            elements[front] = null;
            //
            front = (front + 1) % elements.length;
            //size减 1
            size -- ;
            //返回出队元素
            return frontElement;
        }
        
        ......
        
    }
    
    队列优化 --- 队列扩容及索引映射封装

    由于队列是由数组实现 --- 数组扩容

    入队优化

    public class CircleQueue<E> {
        
        ......
        
        /**
         * 入队 --- 优化
         * @param element
         */
        void enQueue(E element){
            //扩容
            ensureCapacity(size + 1);
            //队尾
            elements[(front + size) % elements.length] = element;
            //size加 1
            size ++;
        }
        
        /**
         * 扩容
         * @param capacity
         */
        private void ensureCapacity(int capacity){
            int oldCapacity = elements.length;
            if (oldCapacity >= capacity) return;
            //新容量为旧容的1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            E[] newElements = (E[]) new Object[newCapacity];
            for (int i = 0; i < size; i++) {
                //获取循环队列的真实位置(固定的索引)的元素
                newElements[i] = elements[(i + front) % elements.length];
            }
            elements = newElements;
            // 重置front
            front = 0;
            System.out.println(oldCapacity + "扩容为" + newCapacity);
        }
        
        ......
        
    }
    

    索引映射封装 --- 维护循环队列的真实索引

    对于(front + X) % elements.length 进行封装

    public class CircleQueue<E> {
        
        ......
            
        /**
         * 入队 --- 优化
         * @param element
         */
        void enQueue(E element){
            //扩容
            ensureCapacity(size + 1);
            //队尾
            elements[index(size)] = element;
            //size加 1
            size ++;
        }
    
        /**
         * 出队 --- 优化
         * @return
         */
        E deQueue(){
            //获取出队元素
            E frontElement = elements[front];
            //将对头置为空
            elements[front] = null;
            //更新头
            front = index(1);
            //size减 1
            size -- ;
            return frontElement;
        }
        
        /**
         * 扩容 --- 优化
         * @param capacity
         */
        private void ensureCapacity(int capacity){
            int oldCapacity = elements.length;
            if (oldCapacity >= capacity) return;
            //新容量为旧容的1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            E[] newElements = (E[]) new Object[newCapacity];
            for (int i = 0; i < size; i++) {
                //获取循环队列的真实位置(固定的索引)的元素
                newElements[i] = elements[index(i)];
            }
            elements = newElements;
            // 重置front
            front = 0;
            System.out.println(oldCapacity + "扩容为" + newCapacity);
        }
        
         /**
         * 返回真实索引
         * @param index 当前索引
         * @return
         */
        private int index(int index){
            return (front + index) % elements.length;
        }
        
        ......
        
    }
    
    完整代码
    /**
     * 循环队列的实现 --- 数组实现
     * @param <E>
     */
    public class CircleQueue<E> {
        /**
         * 记录第0个元素的索引 即队头
         */
        private int front;
        /**
         * 当前队列存储的元素个数
         */
        private int size;
        /**
         * 用来存储元素的数组
         */
        private E[] elements;
        /**
         * 默认数组的容量
         */
        private static final int DEFAULT_CAPACITY = 10;
    
        /**
         * 默认构造函数
         */
        public CircleQueue() {
            this.elements = (E[]) new Object[DEFAULT_CAPACITY];
        }
    
        /**
         * 当前队列存储的元素数量
         * @return
         */
        public int size(){
            return size;
        }
    
        /**
         * 当前队列是否为空
         * @return
         */
        public boolean isEmpty(){
            return size == 0;
        }
    
        /**
         * 入队 --- 优化
         * @param element
         */
        void enQueue(E element){
            //扩容
            ensureCapacity(size + 1);
            //队尾
            elements[index(size)] = element;
            //size加 1
            size ++;
        }
    
        /**
         * 出队 --- 优化
         * @return
         */
        E deQueue(){
            //获取出队元素
            E frontElement = elements[front];
            //将对头置为空
            elements[front] = null;
            //更新头
            front = index(1);
            //size减 1
            size -- ;
            return frontElement;
        }
    
        /**
         * 查看索引为0的元素 即获取队头元素
         * @return
         */
        E front(){
            return elements[front];
        }
    
        /**
         * 扩容 --- 优化
         * @param capacity
         */
        private void ensureCapacity(int capacity){
            int oldCapacity = elements.length;
            if (oldCapacity >= capacity) return;
            //新容量为旧容的1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            E[] newElements = (E[]) new Object[newCapacity];
            for (int i = 0; i < size; i++) {
                //获取循环队列的真实位置(固定的索引)的元素
                newElements[i] = elements[index(i)];
            }
            elements = newElements;
            // 重置front
            front = 0;
            System.out.println(oldCapacity + "扩容为" + newCapacity);
        }
    
        /**
         * 返回真实索引
         * @param index 当前索引
         * @return
         */
        private int index(int index){
            return (front + index) % elements.length;
        }
    
        /**
         * 打印
         * @return
         */
        @Override
        public String toString() {
            StringBuilder string = new StringBuilder();
            string.append("capacity=").append(elements.length)
                    .append(", size=").append(size)
                    .append(", front=").append(front)
                    .append(", [");
            for (int i = 0; i < elements.length; i++) {
                if (i != 0){
                    string.append(", ");
                }
                string.append(elements[i]);
            }
            string.append("]");
            return string.toString();
        }
    }
    

    循环双端队列()

    循环双端队列接口设计
    int front;// 记录第0个元素的索引
    int size;// 当前队列存储的元素个数
    E[] elements;// 用来存储元素的数组
    int size();// 当前队列存储的元素数量
    boolean isEmpty();// 当前队列是否为空
    E front();// 查看索引为0的元素
    void enQueueRear(E element);//从队尾入队
    E deQueueFront();//从队头出队
    void enQueueFront(E element);//从队头入队
    E deQueueRear();//从队尾出队
    E front();//获取队列的头元素
    E rear();//获取队列的尾元素
    
    循环双端队列的实现

    循环双端队列在默认情况是和循环队列是一致的。

    public class CircleDeque<E> {
        /**
         * 记录第0个元素的索引 即队头
         */
        private int front;
        /**
         * 当前队列存储的元素个数
         */
        private int size;
        /**
         * 用来存储元素的数组
         */
        private E[] elements;
        /**
         * 默认数组的容量
         */
        private static final int DEFAULT_CAPACITY = 10;
    
        /**
         * 默认构造函数
         */
        public CircleDeque() {
            this.elements = (E[]) new Object[DEFAULT_CAPACITY];
        }
    
        /**
         * 当前队列存储的元素数量
         * @return
         */
        public int size(){
            return size;
        }
    
        /**
         * 当前队列是否为空
         * @return
         */
        public boolean isEmpty(){
            return size == 0;
        }
    
        /**
         * 从尾部入队 --- 默认
         * @param element
         */
        void enQueueRear(E element){
            //扩容
            ensureCapacity(size + 1);
            //队尾
            elements[index(size)] = element;
            //size加 1
            size ++;
        }
    
        /**
         * 从头部出队 --- 默认
         * @return
         */
        E deQueueFront(){
            //获取出队元素
            E frontElement = elements[front];
            //将对头置为空
            elements[front] = null;
            //更新头
            front = index(1);
            //size减 1
            size -- ;
            return frontElement;
        }
    
        /**
         * 获取队列的头元素
         * @return
         */
        E front(){
            return elements[front];
        }
    
        /**
         * 获取队列的尾元素
         * @return
         */
        E rear(){
            return elements[index(size - 1)];
        }
    
        /**
         * 扩容 --- 优化
         * @param capacity
         */
        private void ensureCapacity(int capacity){
            int oldCapacity = elements.length;
            if (oldCapacity >= capacity) return;
            //新容量为旧容的1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            E[] newElements = (E[]) new Object[newCapacity];
            for (int i = 0; i < size; i++) {
                //获取循环队列的真实位置(固定的索引)的元素
                newElements[i] = elements[index(i)];
            }
            elements = newElements;
            // 重置front
            front = 0;
            System.out.println(oldCapacity + "扩容为" + newCapacity);
        }
    
        /**
         * 打印
         * @return
         */
        @Override
        public String toString() {
            StringBuilder string = new StringBuilder();
            string.append("capacity=").append(elements.length)
                    .append(", size=").append(size)
                    .append(", front=").append(front)
                    .append(", [");
            for (int i = 0; i < elements.length; i++) {
                if (i != 0){
                    string.append(", ");
                }
                string.append(elements[i]);
            }
            string.append("]");
            return string.toString();
        }
        
        ......
            
    }
    
    头部入队和尾部出队
    • 从头部入队

      首先需要对获取真实索引进行改造。元素66进入,使索引0索引6,并将对头标识front指向元素66,一个一个元素进入,以此类推

      (index + front)+ elements.length

    • 从尾部出队

      当队尾出队时,获取真实队尾的位置index(size - 1)及元素elements[rearIndex]

      elements[rearIndex] 置为nullsize减1,返回elements[rearIndex]

    public class CircleDeque<E> {
        
       ......
           
        /**
         * 从头部入队
         * @param element
         */
        void enQueueFront(E element){
            //扩容
            ensureCapacity(size + 1);
            // -1 表示头部入队
            front = index(-1);
            //元素取代原来头部的元素
            elements[front] = element;
            //size加1
            size++;
        }
        
        /**
         * 从尾部出队
         * @return
         */
        E deQueueRear(){
            //获取最后的元素的真实索引
            int rearIndex = index(size - 1);
            //该索引的元素
            E rear = elements[rearIndex];
            //将该索引置为null
            elements[rearIndex] = null;
            //size减1
            size -- ;
            return rear;
        }
        
        /**
         * 返回真实索引 --- 优化
         * @param index 当前索引
         * @return
         */
        private int index(int index){
            index += front;
            // 在头部入队时做的操作
            if (index < 0){
                //为负数时,加上数组长度使原来头部元素位置往挪
                return index + elements.length;
            }
            return index % elements.length;
        }
        
       ......
            
    }
    
    %运算符的优化

    尽量避免使用乘*、除/、模%、浮点数运算,效率低下

    已知 n >= 0, m > 0

    n%m等价于 n - (m > n ? 0 : m) 的前提条件:n < 2m

    • 预期入队索引 = 第0个元素索引 + 当前队列元素个数
    • 如果预期入队索引大于等于数组长度,实际入队索引 = 预期入队索引 - 数组长度
    • 如果预期入队索引小于数组长度,实际入队索引 = 预期入队索引

    循环队列

    public class CircleQueue<E> {
        ......
            
        /**
         * 返回真实索引 --- 优化
         * @param index 当前索引
         * @return
         */
        private int index(int index){
            index += front;
            return index - (elements.length > index ? 0: elements.length);
        }
            
        ......
    }
    

    循环双端队列

    public class CircleDeque<E> {
        
       ......
        
        /**
         * 返回真实索引 --- 优化
         * @param index 当前索引
         * @return
         */
        private int index(int index){
            index += front;
            // 在头部入队时做的操作
            if (index < 0){
                //为负数时,加上数组长度使原来头部元素位置往挪
                return index + elements.length;
            }
            return index - (elements.length > index ? 0 : elements.length);
        }
        
       ......
            
    }
    
    补充 clear() 方法

    循环队列和循环双端队列 clear() 方法一致

    /**
     * 清空队列元素
     */
    public void clear(){
        //清空真实索引的元素
        for (int i = 0; i < size; i++) {
            elements[index(i)] = null;
        }
        size = 0;
        front = 0;
    }
    
    完整代码
    /**
     * 循环双端队列的实现 --- 数组实现
     * @param <E>
     */
    public class CircleDeque<E> {
        /**
         * 记录第0个元素的索引 即队头
         */
        private int front;
        /**
         * 当前队列存储的元素个数
         */
        private int size;
        /**
         * 用来存储元素的数组
         */
        private E[] elements;
        /**
         * 默认数组的容量
         */
        private static final int DEFAULT_CAPACITY = 10;
    
        /**
         * 默认构造函数
         */
        public CircleDeque() {
            this.elements = (E[]) new Object[DEFAULT_CAPACITY];
        }
    
        /**
         * 当前队列存储的元素数量
         * @return
         */
        public int size(){
            return size;
        }
    
        /**
         * 当前队列是否为空
         * @return
         */
        public boolean isEmpty(){
            return size == 0;
        }
    
        /**
         * 从尾部入队 --- 默认
         * @param element
         */
        void enQueueRear(E element){
            //扩容
            ensureCapacity(size + 1);
            //队尾
            elements[index(size)] = element;
            //size加 1
            size ++;
        }
    
        /**
         * 从头部出队 --- 默认
         * @return
         */
        E deQueueFront(){
            //获取出队元素
            E frontElement = elements[front];
            //将对头置为空
            elements[front] = null;
            //更新头
            front = index(1);
            //size减 1
            size -- ;
            return frontElement;
        }
    
        /**
         * 从头部入队
         * @param element
         */
        void enQueueFront(E element){
            //扩容
            ensureCapacity(size + 1);
            // -1 表示头部入队
            front = index(-1);
            //元素取代原来头部的元素
            elements[front] = element;
            //size加1
            size++;
        }
    
        /**
         * 从尾部出队
         * @return
         */
        E deQueueRear(){
            //获取最后的元素的真实索引
            int rearIndex = index(size - 1);
            //该索引的元素
            E rear = elements[rearIndex];
            //将该索引置为null
            elements[rearIndex] = null;
            //size减1
            size -- ;
            return rear;
        }
    
        /**
         * 获取队列的头元素
         * @return
         */
        E front(){
            return elements[front];
        }
    
        /**
         * 获取队列的尾元素
         * @return
         */
        E rear(){
            return elements[index(size - 1)];
        }
    
        /**
         * 扩容 --- 优化
         * @param capacity
         */
        private void ensureCapacity(int capacity){
            int oldCapacity = elements.length;
            if (oldCapacity >= capacity) return;
            //新容量为旧容的1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            E[] newElements = (E[]) new Object[newCapacity];
            for (int i = 0; i < size; i++) {
                //获取循环队列的真实位置(固定的索引)的元素
                newElements[i] = elements[index(i)];
            }
            elements = newElements;
            // 重置front
            front = 0;
            System.out.println(oldCapacity + "扩容为" + newCapacity);
        }
    
        /**
         * 返回真实索引 --- 优化
         * @param index 当前索引
         * @return
         */
        private int index(int index){
            index += front;
            // 在头部入队时做的操作
            if (index < 0){
                //为负数时,加上数组长度使原来头部元素位置往挪
                return index + elements.length;
            }
            //优化
            return index - (elements.length > index ? 0 : elements.length);
        }
    
        /**
         * 清空队列元素
         */
        public void clear(){
            //清空真实索引的元素
            for (int i = 0; i < size; i++) {
                elements[index(i)] = null;
            }
            size = 0;
            front = 0;
        }
        
        /**
         * 打印
         * @return
         */
        @Override
        public String toString() {
            StringBuilder string = new StringBuilder();
            string.append("capacity=").append(elements.length)
                    .append(", size=").append(size)
                    .append(", front=").append(front)
                    .append(", [");
            for (int i = 0; i < elements.length; i++) {
                if (i != 0){
                    string.append(", ");
                }
                string.append(elements[i]);
            }
            string.append("]");
            return string.toString();
        }
    }
    
  • 相关阅读:
    086 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 03 面向对象基础总结 01 面向对象基础(类和对象)总结
    085 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 04 构造方法调用
    jQuery UI组件库Kendo UI使用技巧小分享
    Kendo UI ListView模板功能,让Web开发更轻松
    UI组件套包DevExpress ASP.NET Core v20.2新版亮点:全新的查询生成器
    Devexpress WinForms最新版开发.NET环境配置Visual Studo和SQL Server对应版本
    全新的桌面应用数据可视化呈现方式,Sankey Diagram控件你了解多少?
    java中的递归方法
    连接数据库查询 将查询结果写入exce文件中
    java连接mysql数据查询数据
  • 原文地址:https://www.cnblogs.com/netu/p/15097032.html
Copyright © 2011-2022 走看看