一、队列 Queue
队列是一种特殊的线性表,只能在头尾两端进行操作;
队尾(rear):只能从队尾添加元素,一般叫做 enQueue
,入队
队头(front):只能从队头移除元素,一般叫做 deQueue
,出队
先进先出的原则,First In First Out,FIFO
队列的内部实现可以使用动态数组
或双向链表
实现,优先使用双向链表,因为队列主要是往头尾操作元素。
二、队列的接口设计
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)
双端队列是能在头尾两端添加、删除
的队列;
英文 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(); // 获取队列的尾元素
双端队列实现
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)
的时间复杂度, 这个用数组实现并优化之后的队列就叫做: 循环队列
。
- 使用一个索引变量
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
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;
}
网友评论