队列

队列

队列(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();
    }
}
原文地址:https://www.cnblogs.com/netu/p/15097032.html