一.PriorityQueue的使用
PriorityQueue —— 优先级队列,基于优先对实现的无界队列
Java默认实现基于小根堆实现;
常用方法:
PriorityQueue<Integer> que = new PriorityQueue<>();
que.add(1);
que.add(2);
que.add(3);//队头增添元素
System.out.println(que.toString());//打印
System.out.println(que.contains(2));//判断是否存在
que.offer(2);//队尾增添元素
System.out.println(que.toString());
System.out.println(que.peek());//获取队头元素
que.poll();//删除元素
que.remove();//删除元素
System.out.println(que.toString());
que.clear();//清空队列
System.out.println(que.toString());
二.PriorityQueue底层源码实现:
1.构造函数:
/**
* 默认构造方法,使用默认的初始大小来构造一个优先队列,比较器comparator为空,这里要求入队的元素必须实现Comparator接口
*/
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
/**
* 使用指定的初始大小来构造一个优先队列,比较器comparator为空,这里要求入队的元素必须实现Comparator接口
*/
public PriorityQueue( int initialCapacity) {
this(initialCapacity, null);
}
/**
* 使用指定的初始大小和比较器来构造一个优先队列
*/
public PriorityQueue( int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
// 初始大小不允许小于1
if (initialCapacity < 1)
throw new IllegalArgumentException();
// 使用指定初始大小创建数组
this.queue = new Object[initialCapacity];
// 初始化比较器
this.comparator = comparator;
}
/**
* 构造一个指定Collection集合参数的优先队列
*/
public PriorityQueue(Collection<? extends E> c) {
// 从集合c中初始化数据到队列
initFromCollection(c);
// 如果集合c是包含比较器Comparator的(SortedSet/PriorityQueue),则使用集合c的比较器来初始化队列的Comparator
if (c instanceof SortedSet)
comparator = (Comparator<? super E>)
((SortedSet<? extends E>)c).comparator();
else if (c instanceof PriorityQueue)
comparator = (Comparator<? super E>)
((PriorityQueue<? extends E>)c).comparator();
// 如果集合c没有包含比较器,则默认比较器Comparator为空
else {
comparator = null;
// 调用heapify方法重新将数据调整为一个二叉堆
heapify();
}
}
/**
* 构造一个指定PriorityQueue参数的优先队列
*/
public PriorityQueue(PriorityQueue<? extends E> c) {
comparator = (Comparator<? super E>)c.comparator();
initFromCollection(c);
}
/**
* 构造一个指定SortedSet参数的优先队列
*/
public PriorityQueue(SortedSet<? extends E> c) {
comparator = (Comparator<? super E>)c.comparator();
initFromCollection(c);
}
/**
* 从集合中初始化数据到队列
*/
private void initFromCollection(Collection<? extends E> c) {
// 将集合Collection转换为数组a
Object[] a = c.toArray();
// If c.toArray incorrectly doesn't return Object[], copy it.
// 如果转换后的数组a类型不是Object数组,则转换为Object数组
if (a.getClass() != Object[].class)
a = Arrays. copyOf(a, a.length, Object[]. class);
// 将数组a赋值给队列的底层数组queue
queue = a;
// 将队列的元素个数设置为数组a的长度
size = a.length ;
}
2.PriorityQueue的入队实现
(1)父节点的键值总是小于或等于任何一个子节点的键值
(2)基于数组实现的二叉堆,对于数组中任意位置的n上元素,其左孩子在[2n+1]位置上,其有孩子在[2n+2]位置上,其父类在[n/2]位置上,根节点的位置为[0];
/**
* 添加一个元素
*/
public boolean add(E e) {
return offer(e);
}
/**
* 入队
*/
public boolean offer(E e) {
// 如果元素e为空,则排除空指针异常
if (e == null)
throw new NullPointerException();
// 修改版本+1
modCount++;
// 记录当前队列中元素的个数
int i = size ;
// 如果当前元素个数大于等于队列底层数组的长度,则进行扩容
if (i >= queue .length)
grow(i + 1);
// 元素个数+1
size = i + 1;
// 如果队列中没有元素,则将元素e直接添加至根(数组小标0的位置)
if (i == 0)
queue[0] = e;
// 否则调用siftUp方法,将元素添加到尾部,进行上移判断
else
siftUp(i, e);
return true;
}
入队时siftUp方法:
/**
* 向上移动,x表示新插入元素,k表示新插入元素在数组的位置
*/
private void siftUp(int k, E x) {
// 如果比较器comparator不为空,则调用siftUpUsingComparator方法进行上移操作
if (comparator != null)
siftUpUsingComparator(k, x);
// 如果比较器comparator为空,则调用siftUpComparable方法进行上移操作
else
siftUpComparable(k, x);
}
private void siftUpComparable(int k, E x) {
// 比较器comparator为空,需要插入的元素实现Comparable接口,用于比较大小
Comparable<? super E> key = (Comparable<? super E>) x;
// k>0表示判断k不是根的情况下,也就是元素x有父节点
while (k > 0) {
// 计算元素x的父节点位置[(n-1)/2]
int parent = (k - 1) >>> 1;
// 取出x的父亲e
Object e = queue[parent];
// 如果新增的元素k比其父亲e大,则不需要"上移",跳出循环结束
if (key.compareTo((E) e) >= 0)
break;
// x比父亲小,则需要进行"上移"
// 交换元素x和父亲e的位置
queue[k] = e;
// 将新插入元素的位置k指向父亲的位置,进行下一层循环
k = parent;
}
// 找到新增元素x的合适位置k之后进行赋值
queue[k] = key;
}
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;
}
向上移动,就是不断将新增的元素和其父亲元素进行大小比较,比其父亲小则上移,最终找到合适位置;
3.PriorityQueue出队实现:
出队操作,是要删除根元素,也就是最小的元素,要删除根元素,就要找一个代替者移动到跟位置,想对于被删除的元素来说。就是向下移动;
/**
* 删除并返回队头的元素,如果队列为空则抛出NoSuchElementException异常(该方法在AbstractQueue中)
*/
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
/**
* 删除并返回队头的元素,如果队列为空则返回null
*/
public E poll() {
// 队列为空,返回null
if (size == 0)
return null;
// 队列元素个数-1
int s = --size ;
// 修改版本+1
modCount++;
// 队头的元素
E result = (E) queue[0];
// 队尾的元素
E x = (E) queue[s];
// 先将队尾赋值为null
queue[s] = null;
// 如果队列中不止队尾一个元素,则调用siftDown方法进行"下移"操作
if (s != 0)
siftDown(0, x);
return result;
}
向下移动源代码:
/**
* 下移,x表示队尾的元素,k表示被删除元素在数组的位置
*/
private void siftDown(int k, E x) {
// 如果比较器comparator不为空,则调用siftDownUsingComparator方法进行下移操作
if (comparator != null)
siftDownUsingComparator(k, x);
// 比较器comparator为空,则调用siftDownComparable方法进行下移操作
else
siftDownComparable(k, x);
}
private void siftDownComparable(int k, E x) {
// 比较器comparator为空,需要插入的元素实现Comparable接口,用于比较大小
Comparable<? super E> key = (Comparable<? super E>)x;
// 通过size/2找到一个没有叶子节点的元素
int half = size >>> 1;
// 比较位置k和half,如果k小于half,则k位置的元素就不是叶子节点
while (k < half) {
// 找到根元素的左孩子的位置[2n+1]
int child = (k << 1) + 1;
// 左孩子的元素
Object c = queue[child];
// 找到根元素的右孩子的位置[2(n+1)]
int right = child + 1;
// 如果左孩子大于右孩子,则将c复制为右孩子的值,这里也就是找出左右孩子哪个最小
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue [right]) > 0)
c = queue[child = right];
// 如果队尾元素比根元素孩子都要小,则不需"下移",结束
if (key.compareTo((E) c) <= 0)
break;
// 队尾元素比根元素孩子都大,则需要"下移"
// 交换跟元素和孩子c的位置
queue[k] = c;
// 将根元素位置k指向最小孩子的位置,进入下层循环
k = child;
}
// 找到队尾元素x的合适位置k之后进行赋值
queue[k] = key;
}
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 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;
k = child;
}
queue[k] = x;
}
PriorityQueue在删除过程中,不是直接将根元素删除,然后再将下面的元素做上移,重新补充根元素;而是找出队尾的元素,并在队尾的位置上删除,然后通过根元素的下移,给队尾元素找到一个合适的位置,最终覆盖掉跟元素,从而达到删除根元素的目的。