队列是一种限定为从一端从另进一端出(FIFO)的线性表。从一段插入元素的过程称为入队或进队, 从另一端取出一个元素称为出队。
每次入队在队尾插入新的元素,每次出队时在队首取出一个元素。
利用数组实现的队列
使用数组实现队列时,需要预先设定好队列长度(length), 初始化一个数组array[],将指向第一个元素的指针(size)默认为0。进行入队操作时数据以此向数组尾部递进,每添加一个元素后,size指针都是指向下一连续空间,当size = length 时,队满。出队时只需取出数组下标为i = 0的元素,然后将后面的所有元素依次往前移动一个位置(array[i] = array[i + 1]), 这样做的好处时避免出队时每次取出队首元素后,队首前还存在一片不能使用的地址空间,所以我们需要将浪费掉的空间使用起来。
/**
* @author Huzz
* @created 2021-10-18
* @param <T>
*/
public class ArrayQueue<T> {
private static final int DEFAULT_SIZE = 128;
/**
* 用于保存队列元素的数组
*/
private T[] dataArray;
/**
* 队列长度
*/
private int size;
/**
* 最大长度
*/
private int length;
/**
* 构造方法-初始化队列
* @param type 队列数据类型
* @param size 队列长度,默认128
*/
public ArrayQueue(Class<T> type, int size) {
dataArray = (T[]) Array.newInstance(type, size);
this.size = 0;
length = size;
}
/**
* 构造方法-初始化队列
* @param type 队列数据类型
*/
public ArrayQueue(Class<T> type) {
this(type, DEFAULT_SIZE);
}
/**
* 入队
*
* @param val
*/
public void add(T val) {
dataArray[size++] = val;
}
/**
* 返回队首元素
*
* @return
*/
public T getFirst() {
return dataArray[0];
}
/**
* 出队
*
* @return
*/
public T pop() {
T ret = dataArray[0];
size--;
for (int i = 0; i < size; i++) {
dataArray[i] = dataArray[i + 1];
}
return ret;
}
/**
* 长度
*
* @return
*/
public int size() {
return size;
}
/**
* 是否为空
*
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 队列长度
* @return
*/
public int length(){
return length;
}
}
从上面的 出队方法可得知
/**
* 出队
*
* @return
*/
public T pop() {
T ret = dataArray[0];
size--;
for (int i = 0; i < size; i++) {
dataArray[i] = dataArray[i + 1];
}
return ret;
}
每次出队时后面的所有元素都需要移动, 如果存在频繁出队的操作时,很影响效率,如何提高效率呢?如果将数组的首尾相连,每次入队和出队只做简单的++和--,这样就不存在数据移动了,因此可以使用循环队列来提高出队效率。
循环队列
为了解决数组队列出队时需要移动元素的问题, 提出来循环队列。将一个数据逻辑上首位相接就构成了循环队列。这样虽然不需要移动元素, 但是会牺牲掉一个空间来判断队列状态(队空和对满)。
定义头指针front, 初始化时头指针指向0号空间;
定义尾指针rear, 初始化时尾指针指向0号空间;
当front = rear时, 队空;
入队操作: array[rear++] = data;
出队操作: return array[front++];
当rear + 1 = front时,队满;
注:当头尾指针指向下一圈开始时, 我们不让指针号一直递增下去, 我们通常用当前指针除以循环队列长度的模作为指针的新符合,例如:rear = rear % maxSize。
由于数据沾满数组空间时, 此时rear是指向下一个位置的,也就是指向了front所在的位置,此时rear = front,而前面我们初始化空队时front = rear = 0, 也就是说无论对满和队空时头指针和尾指针都是相等的,所以为了了解队列状态, 我们将牺牲一个空间作为标识, 当rear + 1 = front时队满,当 rear = front时队空。
这样的循环队列只要队不满就可以一直使用了,但是我们如何确定队列元素长度呢?
存在两种情况:
1. 若当前状态下,front <= rear时,长度为 rear - front;
2. 若front > rear时, 长度为maxSize - (front - rear) ;
java实现循环队列:
/**
* 循环队列
* @author Huzz
* @created 2021-10-15 19:1
*/
public class LoopQueue<T> {
private static final int DEFAULT_SIZE = 128;
/**
* 存放队元素的数组
*/
private T[] queueList;
/**
* 头指针
*/
private int front;
/**
* 尾指针
*/
private int rear;
/**
* 最大长度
*/
private int maxSize;
/**
* 初始化队列
*/
public LoopQueue(Class<T> type) {
this(type, DEFAULT_SIZE);
}
public LoopQueue(Class<T> type, int size) {
queueList = (T[]) Array.newInstance(type, size + 1);
rear = 0;
front = 0;
// 多分配一个空间用于确定队满和队空情况
maxSize = size + 1;
}
/**
* 判断队列是否为空
*
* @return
*/
public boolean isEmpty() {
return front == rear;
}
/**
* 判断队列是否已满
*
* @return
*/
public boolean isFull() {
return (rear + 1) % maxSize == front;
}
/**
* 取循环队列的对首元素
*
* @return
*/
public Object getFront() {
return queueList[front];
}
/**
* 入队
*
* @param val
*/
public void push(T val) {
if (isFull()) {
throw new IndexOutOfBoundsException();
}
// 入队操作. 队满时抛出异常
queueList[rear] = val;
rear = (rear + 1) % maxSize;
}
/**
* 出队
*/
public T pop() {
if (isEmpty()) {
return null;
}
T result = queueList[front];
front = (front + 1) % maxSize;
return result;
}
/**
* 获取队列长度
*
* @return
*/
public int size() {
// 1. 当头指针小于尾指针时,长度:len1 = rear - front
// 2. 当头指针大于尾指针时,说明队列从第二圈开始了,
// 空元素段为:front - rear 所以有元素段 maxSize - (front - rear) => len2 = maxSize + front - rear
// 3. 利用同一个表达式表示这两种情况,其中n表示整数常量: (rear - front) + (n * maxSize) % maxSize
return (rear - front + maxSize) % maxSize;
}
}
链表实现队列
利用链表结构可以很容易实现队列,入队时在队位添加元素, 出队时删除队首元素。但是链表不像数组那样可以直接定位到队尾位置, 导致每次入队时都要从队首开始找,具体实现如下。
代码中的SingleLinkedList是我已经写好的单链表数据结构,直接创建后调用链表的基础操作方法即可实现先进先出的队列结构。 想要了解SingleLinkedList的代码请戳这个链接:java实现单链表和双链表数据结构_Huzz童年的纸飞机的博客-CSDN博客
当然,你也可以使用java自带的链表来创建队列。
/**
* @author Huzz
* @created 2021-10-15 9:44
* @param <T>
*/
public class Queue<T> {
private SingleLinkedList<T> linkedList;
private int size;
public Queue() {
linkedList = new SingleLinkedList<T>();
size = 0;
}
/**
* 入队
*
* @param data
*/
public void add(T data) {
linkedList.append(data);
size++;
}
/**
* 长度
*
* @return
*/
public int size() {
return this.size;
}
/**
* 出队
*
* @return
*/
public T get() {
T result = linkedList.getFirst();
linkedList.del(0);
size--;
return result;
}
/**
* 删除队首元素
*/
public void del() {
linkedList.del(0);
size--;
}
}