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();
        }
    }
    
  • 相关阅读:
    PHP使用Memcache来存储session 其他【转载】
    Linux 学习记录 20170218
    php 数组去重
    关于php的array_diff和array_diff_assoc的使用总结
    使用谷歌浏览器调试WEB前端的一些必备调试技巧
    MySql 赋值操作符"="与":="
    移动设备检测类Mobile_Detect.php
    PHP Filter 函数 日常可用
    以符合人类阅读的方式打印php数组【转载】
    JavaWeb:HttpSession(一)
  • 原文地址:https://www.cnblogs.com/netu/p/15097032.html
Copyright © 2011-2022 走看看