数据结构 - 队列

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

一、队列 Queue

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

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

image.png

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

二、队列的接口设计

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

三、队列的实现

public class Queue {
    private List 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 {
    private List 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 {
    // 记录第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 {
    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;
}

你可能感兴趣的:(数据结构 - 队列)