版权声明:本文为博主原创文章,未经博主允许不得转载。
一、队列:Queue
1.1 介绍
- 队列 是一种特殊的线性表,只能在 头尾两端 操作
- 队尾(rear): 只能从 队尾添加元素, 一般叫做 enQueue, 入队
- 对头(front): 只能从队头移除 元素, 一般叫做 deQueue, 出队
- 先进先出的原则,First In First Out,FIFO
- 队列的内部实现可以使用 动态数组 或 双向链表 实现
- 优先使用双向链表,因为队列主要是往头尾操作 元素
1.2 接口设计
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();
}
1.3 方法实现
public class Queue<E> {
private List<E> list = new LinkedList<>();
public int size() {
return list.size();
}
public boolean isEmpty() {
return list.isEmpty();
}
public void enQueue(E element) {
list.add(element);
}
public E deQueue() {
return list.remove(0);
}
public E front() {
return list.get(0);
}
}
1.4 队列分类
1.4.1 双端队列
1.4.1.1 双端队列:Deque
- 双端队列是能在 头尾 两端 添加、删除 的队列
1.4.1.2 双端队列的接口设计&实现
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);
}
}
1.4.2 循环队列
1.4.2.1 循环队列:Circle Queue
- 【数据结构与算法】Java —— 理论 二:动态数组:Dynamic Array 之 1.5 ArrayList 进一步优化
- 队列内部实现也可以用 动态数组 实现,并且将各项接口优化到 O(1) 的时间复杂度, 这个用数组实现并优化之后的队列就叫做 循环队列
- 使用一个索引变量 front 控制第 0 个元素所在位置
- 每一次 出栈,就将 front 位置的元素取出并删除,然后 front 向后 +1
- 每一次 入栈,都根据 front 和当前元素数量计算出入栈元素应该存入的索引,然后将元素存入到数组对应索引的位置上
1.4.2.2 循环队列的接口设计
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.4.2.3 循环队列的实现
1.4.2.3.1 构造方法
- 如果构建的数组空间小于默认空间,则会以默认空间构建数组
public class ArrayList<E> {
private E[] elements;
// 设置elements数组默认的初始化空间
private static final int DEFAULT_CAPACITY = 10;
public CircleQueue() {
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
}
1.4.2.3.2 入队
- 入队前需要考虑两个问题:队列是否需要扩容 和 计算入队实际索引
1.4.2.3.2.1 数组扩容
- 【数据结构与算法】Java —— 理论 二:动态数组:Dynamic Array 之 1.3.2.4 数组扩容
- 扩容后 front 需重置位 0
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;
}
1.4.2.3.2.1 索引计算
- 预期入队索引 = 第0个元素索引 + 当前队列元素个数
- 如果 预期入队索引 大于等于 数组长度,实际入队索引 = 预期入队索引 — 数组长度
- 如果 预期入队索引 小于 数组长度,实际入队索引 = 预期入队索引
private int index(int index) {
index += front;
return index - (index >= elements.length ? elements.length : 0);
}
那么入队的代码如下
public