1、PriorityQueue-底层数据结构
1、PriorityQueue底层使用了数组来存储数据,因为优先级队列:一般使用堆数据结构实现,堆一般使用数组存储。
2、成员属性有:
// 默认容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 队列使用数组保存数据
transient Object[] queue;
// 元素个数,即队列的大小
private int size = 0;
// 比较器,保证优先级,为空表示不指定,则是自然顺序(用元素自己默认的比较器)
private final Comparator<? super E> comparator;
// 操作次数
transient int modCount = 0;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//容量限制
2、PriorityQueue-构造函数
private static final int DEFAULT_INITIAL_CAPACITY = 11;
//默认容量11
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
//指定容量
public PriorityQueue(int initialCapacity) {
this(initialCapacity, null);
}
//默认容量,指定比较器
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
//指定容量,指定比较器,判断传入的容量是否合法,初始化存储元素的数组及比较器
public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
//根据传入的集合构造队列
public PriorityQueue(Collection<? extends E> c) {
if (c instanceof SortedSet<?>) {//SortedSet类型的集合
SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
this.comparator = (Comparator<? super E>) ss.comparator();//取c的比较器
initElementsFromCollection(ss);//元素入队
}
else if (c instanceof PriorityQueue<?>) {//同上
PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
this.comparator = (Comparator<? super E>) pq.comparator();
initFromPriorityQueue(pq);
}
else {
this.comparator = null;//自然顺序
initFromCollection(c);
}
}
@SuppressWarnings("unchecked")
public PriorityQueue(PriorityQueue<? extends E> c) {
this.comparator = (Comparator<? super E>) c.comparator();
initFromPriorityQueue(c);
}
@SuppressWarnings("unchecked")
public PriorityQueue(SortedSet<? extends E> c) {
this.comparator = (Comparator<? super E>) c.comparator();
initElementsFromCollection(c);
}
private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
if (c.getClass() == PriorityQueue.class) {//说明是相同的类型,直接赋值
this.queue = c.toArray();
this.size = c.size();
} else {
initFromCollection(c);
}
}
private void initElementsFromCollection(Collection<? extends E> c) {
Object[] a = c.toArray();
if (c.getClass() != ArrayList.class)//底层是数组,进行数组拷贝
a = Arrays.copyOf(a, a.length, Object[].class);
int len = a.length;
if (len == 1 || this.comparator != null)
for (int i = 0; i < len; i++)
if (a[i] == null)
throw new NullPointerException();//不支持null元素
this.queue = a;
this.size = a.length;
}
private void initFromCollection(Collection<? extends E> c) {
initElementsFromCollection(c);
heapify();//堆化:从有子节点的最靠后元素开始往前,每次都调用siftDown方法来调整的过程。
}
3、PriorityQueue-扩容
1、PriorityQueue-扩容:扩容函数grow()类似于ArrayList里的grow()函数,就是再申请一个更大的数组,并将原数组的元素复制过去。
1-1、[!!!]旧容量小于64时,容量翻倍,旧容量大于等于64,容量只增加旧容量的一半。
2、源码Demo:
private void grow(int minCapacity) {
// 旧容量
int oldCapacity = queue.length;
// 旧容量小于64时,容量翻倍,旧容量大于等于64,容量只增加旧容量的一半
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// 检查是否溢出
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 创建出一个新容量大小的新数组并把旧数组元素拷贝过去
queue = Arrays.copyOf(queue, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
4、PriorityQueue-添加元素
1、添加元素,主要方法有: .add(E e)和.offer(E e)
//向优先队列中插入元素,在插入失败时抛出异常
public boolean add(E e) {
return offer(e);
}
//向优先队列中插入元素,在插入失败时返回false
public boolean offer(E e) {
//不允许放入null元素
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1); //自动扩容
size = i + 1; // 元素个数加1
if (i == 0) //队列原来为空,这是插入的第一个元素
queue[0] = e;
else
//新加入的元素可能会破坏小顶堆的性质,因此需要进行必要的调整。
siftUp(i, e); //调整堆结构,从下而上堆化
return true;
}
2、调堆结构:
//调整节点顺序/位置,因为这是一个最小堆,最小堆必须要严格按照子节点大于父亲节点的顺序做数组中存放。
//在k位置插入项x
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
//有比较器,调用该方法进行节点位置调整
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
//没有比较器,调用该方法进行节点位置调整
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1; //右移一位,相当于/2,与计算公式:parentNo = (nodeNo-1)/2对应。
Object e = queue[parent];
//调用比较器的比较方法:比较可以是元素的自然顺序,也可以是依靠比较器的顺序。
//从k指定的位置开始,将x逐层与当前点的parent进行比较并交换
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
4-1、PriorityQueue-offer(E e)-堆化过程示意图
5、PriorityQueue-获取元素
1、获取元素,主要方法有: .element(E e)和.peek(E e)。【获取但不删除队首元素,也就是队列中权值最小的那个元素。】
2、源码:
//当方法失败时返回null
public E peek() {
if (size == 0)
return null;
return (E) queue[0]; //根据小顶堆的性质,堆顶那个元素就是全局最小的那个;由于堆用数组表示,根据下标关系,0下标处的那个元素既是堆顶元素。所以直接返回数组0下标处的那个元素即可。
}
//当方法失败时抛出异常
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
5-1、PriorityQueue-peek()-示意图
6、PriorityQueue-删除元素
1、删除元素,涉及方法:.remove()和.poll()。【获取并删除队首元素】
2、源码:
//获取并删除队首元素,当方法失败时抛出异常
public boolean remove(Object o) {
//找到第一个满足o.equals(queue[i])元素的下标
int i = indexOf(o);
if (i == -1)
return false;
else {
removeAt(i);
return true;
}
}
//删除元素位置不同,按不同情况处理
private E removeAt(int i) {
assert i >= 0 && i < size;
modCount++;
int s = --size;
if (s == i) //1. 删除的是最后一个元素。直接删除即可,不需要调整。
queue[i] = null;
else { //2. 删除的不是最后一个元素,从删除点开始以最后一个元素为参照调用一次siftDown()即可。
E moved = (E) queue[s];
queue[s] = null;
siftDown(i, moved);
if (queue[i] == moved) {
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}
//获取并删除队首元素,当方法失败时返回null
public E poll() {
if (size == 0)
return null;
int s = --size;
modCount++;
E result = (E) queue[0];//0下标处的那个元素就是最小的那个
E x = (E) queue[s];
queue[s] = null;
//由于删除操作会改变队列的结构,为维护小顶堆的性质,需要进行必要的调整。
if (s != 0)
siftDown(0, x);
return result;
}
//调用siftDown()方法对堆进行调整,最后返回原来0下标处的那个元素(也就是最小的那个元素)
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
//从k指定的位置开始,将x逐层向下与当前点的左右孩子中较小的那个交换,直到x小于或等于左右孩子中的任何一个为止。
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
// 只需要比较一半就行了,因为叶子节点占了一半的元素
int half = size >>> 1; // loop while a non-leaf
//首先找到左右孩子中较小的那个,记录到c里,并用child记录其下标。【自上而下堆化】
while (k < half) {
//寻找子节点的位置,这里加1是因为元素从0号位置开始
int child = (k << 1) + 1; //leftNo = parentNo*2+1
//左子节点的值
Object c = queue[child];
//右子节点的位置
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
//左右节点取其小者
c = queue[child = right];
if (key.compareTo((E) c) <= 0)
break;
// 如果比最小的子节点大,则交换位置
queue[k] = c;
k = child;
}
// 找到正确的位置,放入元素
queue[k] = key;
}
//从k指定的位置开始,将x逐层向下与当前点的左右孩子中较小的那个交换,直到x小于或等于左右孩子中的任何一个为止。
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
//首先找到左右孩子中较小的那个,记录到c里,并用child记录其下标。【自上而下堆化】
while (k < half) {
int child = (k << 1) + 1;//leftNo = parentNo*2+1
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;//然后用c取代原来的值
k = child;
}
queue[k] = x;
}
6-1、PriorityQueue-删除元素-.poll()-.siftDown()图解过程
6-2、PriorityQueue-删除元素-.remove(Object o)-两种删除情况图解
7、PriorityQueue-建堆过程
1、PriorityQueue的建堆过程和最大堆的建堆过程基本上一样的,从有子节点的最靠后元素开始往前,每次都调用siftDown方法来调整。这个过程也叫做heapify。
2、源码:
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
//siftDown的过程是将一个节点和它的子节点进行比较调整,保证它比它所有的子节点都要小。这个调整的顺序是从当前节点向下,一直调整到叶节点。
siftDown(i, (E) queue[i]);
}
8、PriorityQueue-应用场景以及测试案例
1、应用场景,队列中元素,需要按照优先队列,也就是队列里面的元素遵从一定的比较规则(比较器比较)有序。
2、测试Demo:
public class PriorityQueueTest {
public static void main(String[] args) {
PriorityQueue<Integer> queue = Queues.newPriorityQueue();
test2(queue);
while (!queue.isEmpty()){
System.out.println(queue.poll());
}
}
private static void test1(Queue queue){
queue.add(1);
queue.add(8);
queue.add(3);
queue.add(5);
queue.add(2);
queue.add(448);
queue.add(328);
queue.add(18);
queue.add(558);
queue.add(38);
queue.add(98);
}
private static void test2(Queue queue){
queue.add("a");
queue.add("v");
queue.add("d");
queue.add("s");
queue.add("z");
queue.add("da");
queue.add("dda");
queue.add("tgfa");
queue.add("yha");
queue.add("ihfa");
queue.add("mdtad");
}
}
输出结果:
1
2
3
5
8
18
38
98
328
448
558
a
d
da
dda
ihfa
mdtad
s
tgfa
v
yha
z