队列的定义
队列是只允许在队尾进行插入操作,在队头进行删除操作、先进先出的线性表。
如图:
其中A1为队头元素,A7为队尾元素。
队列的数据存储结构
顺序存储结构
队列的顺序存储结构,需要创建数组来实现:
public class ArrayQueue<T> {
private int defalut_size = 10;//默认大小
private Object[] mObjects;//当前存放数据的数组
private int front;//队头下标
private int rear;//队尾下标
public ArrayQueue() {
//初始化对列数组
mObjects = new Object[defalut_size];
}
//入队
public void enQueue(T t) {
//队尾下标不大于默认长度
if (rear < defalut_size) {
mObjects[rear] = t;
rear++;
}
}
//出队
public T deQueue() {
//队头下标不超过队尾下标
if (front < rear) {
T t = (T) mObjects[front];
mObjects[front] = null;
front++;
return t;
}
return null;
}
//打印队列状态
public void getQueuqData() {
for (int i = 0; i < mObjects.length; i++) {
System.out.println("数组下标: " + i + " data: " + mObjects[i]);
}
}
}
当调用出队方法后,会把原来数组front的位置空出来,但假如此时队尾的下标已到达最大值,再往后入队的时候,就会导致数组越界,但队头明明还存在一个空位置,这就是“假溢出”现象。如下图:
要解决假溢出,就用到循环对列。只要对列尾指针rear达到数组最大时,把队尾指针指向队头,达到头尾循环。
//入队
public void enQueue(T t) {
//是否对列已满
if ((rear + 1) % defalut_size == front) {
rear = (rear + 1) % defalut_size;
mObjects[rear] = t;
}else {
try {
throw new Exception("对列已满");
} catch (Exception e) {
e.printStackTrace();
}
}
}
链式存储结构
对列的链式存储结构,其实是线性表的单链表,只不过是限制了只能尾进头出。队头指针叫做头结点,队尾指针叫做尾节点。
java代码实现如下:
public class LinkQueue<T> {
private int size;//对列大小
private Node front;//头结点
private Node rear;//尾节点
private class Node<T> {
private Node next;
private T data;
public Node(T data) {
this.data = data;
}
}
public LinkQueue() {
}
//初始化头结点等于尾节点
public LinkQueue(T t) {
this.front = this.rear = new Node(t);
size++;
System.out.println("入队数据:" + t);
}
//入队
public void enQueue(T t) {
//初始化节点
Node<T> node = new Node<>(t);
//表示对列为空,则插入的头结点与尾节点一直
if (front == null) {
rear = front = node;
} else {
//将尾节点的next指针指向新节点,并把新节点作为新节点。
rear = rear.next = node;
}
System.out.println("入队数据:" + t);
size++;
}
//出队
public Object deQueue() {
//表示队列中大于一个节点
if (front != rear) {
//将头结点赋值给next;
Node oldFront = front;
//把原头结点的next作为新的头结点。
front = oldFront.next;
//对列大小减一
size--;
System.out.println("出队数据:" + oldFront.data);
return oldFront.data;//返回出队数据
} else {
Object data = front.data;
//如果对列只有一个节点,直接出队,把头结点尾节点赋为空,返回出队数据。
front = rear = null;
size = 0;
System.out.println("出队数据:" + data);
return data;
}
}
public int getSize() {
System.out.println("队列大小:" + size);
return size;
}
public void printQueue() {
Node<T> mFront = front;
if (mFront != null) {
System.out.println("剩下节点:" + mFront.data);
while (mFront.next != null) {
mFront = mFront.next;
System.out.println("剩下节点:" + mFront.data);
}
}
}
}
java中的对列
java中的对列是一个接口Queue
package java.util;
public interface Queue<E> extends Collection<E> {
boolean add(E var1);
boolean offer(E var1);
E remove();
E poll();
E element();
E peek();
}
其中
public abstract class AbstractQueue<E>
extends AbstractCollection<E>
implements Queue<E> {
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
}
具体分析一下PriorityQueue这个类。
入队
//实现add方法最终也是调用了offer方法
public boolean add(E e) {
return offer(e);
}
//入队方法
public boolean offer(E e) {
//如果入队数据为空,抛空指针异常
if (e == null)
throw new NullPointerException();
//计数器加一
modCount++;
int i = size;//获取当前已经存在的元素数量
if (i >= queue.length)//当存在的数量大于或者等于存储的数组长度时,扩容
grow(i + 1);
size = i + 1;//实际长度加一
if (i == 0)//如果列为空,直接放入队头位置,否则入队
queue[0] = e;
else
siftUp(i, e);//这个队列是一个优先级对列,会通过比较器,把新加入的元素放到合适位置,实现队列元素的优先级。
return true;
}
先看扩容方法grow
private void grow(int minCapacity) {
int oldCapacity = queue.length;//获取旧数组长度
// 如果当前数组长度小于64,则长度扩大为2*oldCapacity +2,
//否则oldCapacity +oldCapacity/2的长度
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// 如果新长度大于Integer.MAX_VALUE - 8; 则为Integer.MAX_VALUE 否则为
//Integer.MAX_VALUE - 8
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//扩容copy新数组
queue = Arrays.copyOf(queue, newCapacity);
}
出队
public E poll() {
if (size == 0)
return null;
int s = --size;//大小减一
modCount++;//计数器加一
E result = (E) queue[0];//拿队头元素
E x = (E) queue[s];//获取最后元素
queue[s] = null;//最后元素赋空
if (s != 0)
siftDown(0, x);//利用二叉树重新移动元素。
return result;
}
默认调用siftDownComparable方法利用二叉树移动元素:
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
// 获取数组中间坐标
int half = n >>> 1; // loop while a non-leaf
// k其实是half的父节点下标,若k >= half,则表明k在数组中已经没有子节点
while (k < half) {
// k的左子节点下标
int child = (k << 1) + 1; // assume left child is least
// 获取左子节点的值
Object c = array[child];
// 获取右子节点下标
int right = child + 1;
// 选取左右子节点的最小值
if (right < n &&
((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
c = array[child = right];
// key <= c说明已经按照升序排序,跳出循环
if (key.compareTo((T) c) <= 0)
break;
// 否则,将子节点的值放在父节点上
array[k] = c;
// 将child当作下次比较的k
k = child;
}
queue[k] = key;
}
可以看到 PriorityBlockingQueue底层使用线性表的顺序存储结构来实现。
总结
对列的操作时间复杂度为常数时间,顺序存储结构要提前申请空间,必要时还需要扩容来防止溢出,同时还要防止假溢出现象。而链式存储结构,每次释放和申请节点都需要时间开销,如果在长度固定的情况下,建议用顺序存储结构,如果无法预知对列长度,则需要使用链式存储结构。