代码传送门,欢迎star:https://github.com/mcrwayfun/java-data-structure
队列是一种限定存取位置的线性表。它只允许在表的一端插入,在另一端删除。允许插入的一端叫做队尾,允许删除的一端叫做队头。
每次在队尾加入元素,因此入队的顺序为a1,a2,a3,…,an,最先入队的元素最先出队,所以队列具有的这种特性成为先进先出FIFO(First In First Out)
队列是一种特殊的线性表,其包含了一般线性表的操作,抽象数据类型ADT可以概括为两种:基于数组的存储表示和基于链表的存储表示。基于数组实现的是栈称为顺序队列,基于链表实现的栈称为链式队列。基本方法可以概括为
public interface Queue<E> {
// 判断队列是否为空
boolean isEmpty();
// 入队
void push(E data);
// 出队
E pop();
// 查询front
E peek();
// 清空队列
void clear();
// 返回当前队列中的元素个数
int length();
}
顺序栈基于数组来存储表示,申明一个一维数组来存储数据
private Object[] elementData;
定义两个整型变量front和rear,分别表示为队头和队尾;队头front保存着即将出队的元素的索引,而队尾rear保存着下一个即将入队元素的索引。DEFAULT_SIZE为初始化数组的默认长度,capacity为数组中元素的个数
// 队列默认长度
private int DEFAULT_SIZE = 10;
// 保存数组的长度。
private int capacity;
// 队列头元素位置
private int front = 0;
// 队列尾元素位置
private int rear = 0;
提供一个无参构造函数来初始化数组,数组长度为DEFAULT_SIZE
/**
* 初始化数组长度和数组
*/
public SequenceQueue() {
this.capacity = DEFAULT_SIZE;
elementData = new Object[capacity];
}
提供value作为队列第一个元素,initSize作为数组的初始化长度
/**
* 以指定长度的数组来创建队列
*
* @param value 指定顺序中的第一个元素
* @param initSize 数组长度
*/
public SequenceQueue(E value, int initSize) {
this.capacity = initSize;
elementData = new Object[capacity];
elementData[0] = value;
rear++;
}
检查队列是否已经满了,若不满则在队尾入队,且rear++
/**
* 入队
*
* @param data
* @throws IndexOutOfBoundsException
*/
@Override
public void push(E data) {
// 检查队列是否已经满了
checkQueueIsFull();
elementData[rear++] = data;
}
检查队列是否为空,若不为空则在队头出队,且front++
/**
* 出队,推出元素
*
* @return
* @throws IndexOutOfBoundsException
*/
@Override
@SuppressWarnings("unchecked")
public E pop() {
// 检查队列是否为空
checkQueueIsEmpty();
E oldValue = (E) elementData[front];
// 释放队列已经出栈的元素
elementData[front++] = null;
return oldValue;
}
/**
* 推出元素但不出队
*
* @return
* @throws IndexOutOfBoundsException
*/
@Override
@SuppressWarnings("unchecked")
public E peek() {
// 检查队列是否为空
checkQueueIsEmpty();
return (E) elementData[front];
}
/**
* 获取顺序队列的大小
*
* @return
*/
@Override
public int length() {
return rear - front;
}
链式队列使用结点的方式来存储表示数据,不用预先分配存储空间。使用front指针表示队头,rear指针表示队尾,size来统计当前队列中的结点数
/**
* 定义一个内部类Node,实现链表队列的结点
*
* @param
*/
private static class Node<E> {
E item;
Node next;
public Node() {
}
public Node(E item, Node next) {
this.item = item;
this.next = next;
}
}
/**
* 队列头指针
*/
private Node front;
/**
* 队列尾指针
*/
private Node rear;
/**
* 队列包含的结点数
*/
private int size;
无参构造函数
/**
* 无参构造函数
*/
public LinkQueue() {
front = null;
rear = null;
}
以特定的数据来构造链表队列的头结点
/**
* 以特定的数据来构造链表队列的头结点
*
* @param data
*/
public LinkQueue(E data) {
front = new Node<>(data, null);
rear = front;
size++;
}
分两种情况,队列为空和不为空。若队列为空,则队头指针和队尾指针均指向新结点;若不为空,则队尾指针rear.next = newNode,且newNode成为新的队尾
/**
* 入队
*
* @param data
*/
@Override
public void push(E data) {
Node newNode = new Node<>(data, null);
// 当前队列为空
if (isEmpty()) {
front = newNode;
rear = newNode;
} else {
// 队列存在元素
// 尾结点的next指向新结点
rear.next = newNode;
// 新结点成为新的尾结点
rear = newNode;
}
size++;
}
先检查队列是否为空,若不为空才能执行出队操作。出队元素为当前front指针指向的结点
/**
* 出队,并删除头元素
*
* @return
* @throws IndexOutOfBoundsException
*/
@Override
public E pop() {
// 检测队列是否空
checkQueueIsEmpty();
Node oldFront = front;
front = front.next;
oldFront.next = null;
size--;
return oldFront.item;
}
当前front指向的结点即为队头
/**
* 查找队列的front元素
*
* @return
* @throws IndexOutOfBoundsException
*/
@Override
public E peek() {
// 检测队列是否空
checkQueueIsEmpty();
return front.item;
}
/**
* 返回队列长度
*
* @return
*/
@Override
public int length() {
return size;
}
这是一个顺序队列,由图中可以得知,当front == rear
的时候,队列为空。当rear = maxSize
(maxSize = 数组长度),队列满,如果再加入新的元素,则会产生溢出。但是,这种溢出是一种假溢出,因为数组的前端可能还存在空位置,为了能够充分利用数组中的元素,把数组的前端和后端连接起来,形成一个环形的表,这就是循环队列。
循环队列首尾相接,当队头指针front和队尾rear进到maxSize-1时,在前进一个位置就自动到0,所以:
**队头指针进1:front = (front + 1)% maxSize
队尾指针进1:rear = (rear + 1) % maxSize**
队列为空的判断条件为front == rear
,为了区别,队满的判断条件为front == (rear + 1) % maxSize
,即当rear指向front的前一位置时就认为队列已经满了。还有一种判断队列满的方法,定义一个flag,当出队时,让flag = 0;入队时,让flag = 1。当遇到front == rear
,若flag = 0,则队列空;若flag = 1,则队列满
// 队列默认长度
private int DEFAULT_SIZE = 10;
// 用来保存队列元素的数组
private Object[] elementData;
// 保存数组的长度。
private int capacity;
// 队列头元素位置
private int front = 0;
// 队列尾元素位置
private int rear = 0;
上述代码是操作循环队列申明的变量,用数组elementData
来存储数据,capacity
标识数组的长度。因为存在数组,我们可以利用elementData[rear]
和elementData[front]
来替换flag。当遇到front == rear
时,若elementData[rear] == null
,则队列空;若elementData[front] != null
,则队列满
无参构造函数,使用默认长度DEFAULT_SIZE
来初始化数组长度
public LoopQueue() {
this.capacity = DEFAULT_SIZE;
elementData = new Object[capacity];
}
以初始元素和初始长度来构建循环队列
/**
* 以初始元素和初始长度来构建循环队列
*
* @param data
* @param initSize
*/
public LoopQueue(E data, int initSize) {
this.capacity = initSize;
elementData = new Object[capacity];
elementData[0] = data;
rear++;
}
入队前先判断队是否满了,若满了则不能入队。将data放入数组,且rear++。判断rear == capacity
,若等于则从下标0开始
/**
* 入队
*
* @param data
* @throws IndexOutOfBoundsException
*/
@Override
public void push(E data) {
// 判断队列是否已经满了
checkQueueIsFull();
elementData[rear++] = data;
// rear到头则rear转头
rear = rear == capacity ? 0 : rear;
}
出队前先判断队列是否为空,若空了则不能出队。出队后front++,判断front == capacity
,若等于则从下标0开始
/**
* 出队并删除头元素
*
* @return
* @throws IndexOutOfBoundsException
*/
@Override
@SuppressWarnings("unchecked")
public E pop() {
// 判断队列是否为空
checkQueueIsEmpty();
E oldValue = (E) elementData[front];
elementData[front++] = null;
// front到头则front转头
front = front == capacity ? 0 : front;
return oldValue;
}
/**
* 查找队列的第一个元素
*
* @return
* @throws IndexOutOfBoundsException
*/
@Override
@SuppressWarnings("unchecked")
public E peek() {
// 判断队列是否为空
checkQueueIsEmpty();
return (E) elementData[front];
}
分两种情况,如果front < rear,有效元素就是front到rear之间的元素,直接用rear - front
即可;如果front >= rear,有效元素为front->capacity之间,0->front之间的
/**
* 获取循环队列的大小
*
* @return
*/
@Override
public int length() {
if (isEmpty()) {
return 0;
} else {
return rear - front > 0 ? rear - front
: capacity - (front - rear);
}
}
/**
* 判断队列是否为空
*
* @return
*/
@Override
public boolean isEmpty() {
return rear == front
&& elementData[rear] == null;
}
/**
* 判断队列是否已经满了,若满了则抛出IndexOutOfBoundsException
*/
private void checkQueueIsFull() {
if (rear == front && elementData[front] != null) {
// throw exception
}
}