一.PriorityQueue的数据结构
优先级队列本质是通过二叉堆实现的。因此在对优先级队列的集合进行学习之前,需要了解一下堆这种数据结构。堆其实就是一棵完全二叉树,可以分为大根堆和小根堆。
小根堆:父节点小于子节点
大根堆:父节点大于子节点。(图的结构原理同上)
二.PriorityQueue的源码分析
1.继承关系
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable
继承自AbstractQueue,实现了Serializable接口
2.相关的属性
private static final long serialVersionUID = -7720805057305804111L;
private static final int DEFAULT_INITIAL_CAPACITY = 11;//队列默认初始容量为11
private transient Object[] queue;//存放队列元素的Object数组
private int size = 0;//队列的长度
private final Comparator<? super E> comparator;//比较器
private transient int modCount = 0;//被修改的次数
3.构造函数
①无参构造函数:
优先级队列的大小为11,队列中的元素采用自然顺序排序
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
②带参构造函数
优先级队列大小为指定容量大小,队列中的元素采用自然顺序排序
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
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
4.主要方法
①add()方法,向队列中添加元素。
public boolean add(E e) {
return offer(e);
}
我们发现add方法实际上调用了offer方法,这个方法的源码来瞅一下
为什么要向上调整?当然是为了满足小根堆的特点了。每一次插入先将元素插在队列末尾,依次向上调整,使得父节点总比子节点小。以图为例
初始状态
向上调整一次
再次向上调整,使得2跑到了堆顶(堆顶就是最小元素)
看一看siftUp函数的底层实现
//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;//父节点的下标
Object e = queue[parent];//父节点的元素
if (key.compareTo((E) e) >= 0)//x比e大就跳出循环,即找到了x应该插入的位置
break;
queue[k] = e;
k = parent;
}
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)//x>=e
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
②peek():获取堆顶元素
public E peek() {
if (size == 0)
return null;
return (E) queue[0];//返回下标为0位置即堆顶位置的元素
}
③poll():删除堆顶元素,并返回堆顶。
删除的流程:取出queue[0]元素,然后将queue[size-1]插入到queue[0],然后向下调整保证小根堆的特点
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;
}
接下来看siftDown()
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super 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 &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)//下标未越界并且左孩子比右孩子大
c = queue[child = right];//c中存储的实际上是左右孩子中较小元素
if (key.compareTo((E) c) <= 0)//要插入元素比较小的孩子小,跳出循环
break;
queue[k] = c;
k = child;
}
queue[k] = key;
}
④remove(Object o):删除指定元素
public boolean remove(Object o) {
int i = indexOf(o);
if (i == -1)
return false;
else {
removeAt(i);
return true;
}
}
remove(Object o)方法的思想为:
1)找到这个元素在数组中的位置,如果没有找到,则直接返回false。否则进行2
2)调用removeAt进行删除并调整操作。
⑤clear():清空队列元素
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
queue[i] = null;
size = 0;
}
三. 用PriorityQueue实现大根堆
PriorityQueue默认实现的是小根堆,可以通过以下方法实现大根堆
PriorityQueue<Integer> maxHeap=new PriorityQueue<Integer>(10, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
重写比较器里的compare方法即可
四.例:统计十万个数据重复次数最少的十个
可以使用大根堆
public static void ReMinTen(){
/**
* 重复次数最少的十位数
*/
PriorityQueue<Map.Entry<Integer, Integer>> q = new PriorityQueue<Map.Entry<Integer, Integer>>(10,new Comparator<Map.Entry<Integer, Integer>>(){
@Override
public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
return o2.getValue() - o1.getValue();
}
});
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
/**
* 给 HashMap 赋值
*/
for(int i = 0; i < 100000;i++){
Random random = new Random();
int key = random .nextInt(10000);//键
if(map.containsKey(key)){//如果 map 已经包含了这个键.使它的 value 加1.
int value = map.get(key);
map.put(key,value + 1);
}else{
map.put(key,1);
}
}
/**
* 对键值对遍历
*/
Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
/**
* 遍历 HashMap 并把里面的值加入到队列中
*/
while(iterator.hasNext()){
Map.Entry<Integer, Integer> next = iterator.next();
Integer key = next.getKey();
Integer value = next.getValue();
if(q.size() < 10){
q.add(next);
}else{
if(value < q.peek().getValue()){
q.remove();
q.add(next);
}
}
}
for(int i =0 ;i < q.size();){
System.out.print(q.remove() + " ");
}
}