队列
队列(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] 置为null,size减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();
}
}