美文网首页
数据结构 - 队列

数据结构 - 队列

作者: 我阿郑 | 来源:发表于2021-12-18 09:33 被阅读0次

    数据结构和算法动态可视化网站

    一、队列 Queue

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

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

    image.png

    队列的内部实现可以使用动态数组双向链表实现,优先使用双向链表,因为队列主要是往头尾操作元素。

    二、队列的接口设计

    public class Queue<E> {
        // 使用双向链表实现队列
        private List<E> list = new LinkedList<>();
        // 元素的数量
        public int size();
        // 是否为空
        public boolean isEmpty();
        // 入队
        public void enQueue(E element);
        // 出队
        public E deQueue();
        // 获取队列的头元素
        public E front();
    }
    

    三、队列的实现

    public class Queue <E>{
        private List<E> list = new LinkedList<>();
        public void enQueue(E element){
            list.add(element);
        }
        public E deQueue(){
            return list.remove(0);
        }
        public int size(){
            return list.size();
        }
        public void clear(){
            list.clear();
        }
        public E top(){
            return list.get(0);
        }
        public boolean isEmpty(){
            return list.isEmpty();
        }
    }
    

    四、双端队列 (Deque)

    双端队列是能在头尾两端添加、删除的队列;
    英文 dequedouble ended queue 的简称

    双端队列接口设计

    int size(); // 元素的数量
    boolean isEmpty(); // 是否为空
    void clear(); // 清空
    void enQueueRear(E element); // 从队尾入队
    E deQueueFront(); // 从队头出队
    void enQueueFront(E element); // 从队头入队
    E deQueueRear(); // 从队尾出队
    E front(); // 获取队列的头元素
    E rear(); // 获取队列的尾元素
    

    双端队列实现

    public class Deque<E> {
        private List<E> list = new LinkedList<>();
        
        // 元素的数量
        public int size() {
            return list.size();
        }
        // 是否为空
        public boolean isEmpty() {
            return list.isEmpty();
        }
        // 从队头出队
        public E deQueueFront() {
            return list.remove(0);
        }
        // 从队头入队
        public void enQueueFront(E element) {
            list.add(0, element);
        }
        // 从队尾入队
        public void enQueueRear(E element) {
            list.add(element);
        }
        // 从队尾出队
        public E deQueueRear() {
            return list.remove(list.size() - 1);
        }
        // 获取队列的头元素
        public E front() {
            return list.get(0);
        }
        // 获取队列的尾元素
        public E rear() {
            return list.get(list.size() - 1);
        }
    }
    

    五、循环队列

    队列内部实现也可以用动态数组实现,并且将各项接口优化到O(1)的时间复杂度, 这个用数组实现并优化之后的队列就叫做: 循环队列

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

    循环队列的接口设计

    public class CircleQueue<E> {
        // 记录第0个元素的索引
        private int front;
        // 当前队列存储的元素个数
        private int size;
        // 用来存储元素的数组
        private E[] elements;
        // 当前队列存储的元素数量
        public int size();
        // 当前队列是否为空
        public boolean isEmpty();
        // 入队
        public void enQueue(E element);
        // 出队
        public E deQueue();
        // 查看索引为0的元素
        public E front();
    }
    

    循环队列的实现

    1、构造方法

    public class ArrayList<E> {
        private E[] elements;
        // 设置elements数组默认的初始化空间
        private static final int DEFAULT_CAPACITY = 10;
        
        public CircleQueue() {
            elements = (E[]) new Object[DEFAULT_CAPACITY];
        }
    }
    

    2、入队

    • 入队前需要考虑两个问题:队列是否需要扩容计算入队实际索引

    扩容相关内容请阅读《动态数组》一章

    • 扩容后front 需要重置为0
    image.png
    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;
    }
    

    3、索引计算

    • 预期入队索引 = 第0个元素索引 + 当前队列元素个数。
    • 如果预期入队索引大于等于数组长度,实际入队索引 = 预期入队索引 - 数组长度。
    • 如果预期入队索引小于数组长度,实际入队索引 = 预期入队索引。
    // 索引计算
    private int index(int index) {
        index += front;
        return index - (index >= elements.length ? elements.length : 0);
    }
    
    // 入队的代码
    public void enQueue(E element) {
        // 数组扩容判断
        ensureCapacity(size + 1);
        // 索引计算,并赋值
        elements[index(size)] = element;
        // size加一
        size++;
    }
    

    4、出队

    出队后需要更新front

    public E deQueue() {
        // 获取出队元素
        E frontElement = elements[front];
        // 将索引位置致空
        elements[front] = null;
        // 更新font
        front = index(1);
        // size减一
        size--;
        // 返回出队元素
        return frontElement;
    }
    

    相关文章

      网友评论

          本文标题:数据结构 - 队列

          本文链接:https://www.haomeiwen.com/subject/rfevxrtx.html