Java 1.5版本后就提供了一个具备了小根堆性质的数据结构也就是优先队列PriorityQueue。
//PriorityQueue默认是一个小顶堆,然而可以通过传入自定义的Comparator函数来实现大顶堆。
实际上是一个堆(不指定Comparator时默认为最小堆)
队列既可以根据元素的自然顺序来排序,也可以根据 Comparator来设置排序规则。
队列的头是按指定排序方式的最小元素。如果多个元素都是最小值,则头是其中一个元素。
新建对象的时候可以指定一个初始容量,其容量会自动增加。
注意1:该队列是用数组实现,但是数组大小可以动态增加,容量无限。
注意2:队列的实现不是同步的。不是线程安全的。如果多个线程中的任意线程从结构上修改了列表, 则这些线程不应同时访问 PriorityQueue实例。保证线程安全可以使用PriorityBlockingQueue 类。
注意3:不允许使用 null 元素。
注意4:插入方法(offer()、poll()、remove() 、add() 方法)时间复杂度为O(log(n)) ;
remove(Object) 和 contains(Object) 时间复杂度为O(n);
检索方法(peek、element 和 size)时间复杂度为常量。
注意5:方法iterator()中提供的迭代器并不保证以有序的方式遍历优先级队列中的元素。(原因可参考PriorityQueue的内部实现)
如果需要按顺序遍历,可用Arrays.sort(pq.toArray())。
注意6:可以在构造函数中指定如何排序。如:
PriorityQueue()
使用默认的初始容量(11)创建一个 PriorityQueue,并根据其自然顺序来排序其元素(使用 Comparable)。
PriorityQueue(int initialCapacity)
使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序来排序其元素(使用 Comparable)。
PriorityQueue(int initialCapacity, Comparator<? super E> comparator)
使用指定的初始容量创建一个 PriorityQueue,并根据指定的比较器comparator来排序其元素。
注意7:此类及其迭代器实现了 Collection 和 Iterator 接口的所有可选 方法。
PriorityQueue对元素采用的是堆排序,头是按指定排序方式的最小元素。堆排序只能保证根是最大(最小),整个堆并不是有序的。
方法iterator()中提供的迭代器可能只是对整个数组的依次遍历。也就只能保证数组的第一个元素是最小的。
//PriorityQueue默认是一个小顶堆,然而可以通过传入自定义的Comparator函数来实现大顶堆
PriorityQueue的数据结构
PriorityQueue的逻辑结构是一棵完全二叉树,存储结构其实是一个数组。逻辑结构层次遍历的结果刚好是一个数组。
PriorityQueue的操作
add(E e) 和 offer(E e) 方法
add(E e) 和 offer(E e) 方法都是向PriorityQueue中加入一个元素,其中add()其实调用了offer()方法如下:
public boolean add(E e) {
return offer(e);
}
下面主要看看offer()方法的作用:
如上图调用 offer(4)方法后,往堆中压入4然后从下往上调整堆为小顶堆。offer()的代码实现:
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
//如果压入的元素为null 抛出异常
int i = size;
if (i >= queue.length)
grow(i + 1);
//如果数组的大小不够扩充
size = i + 1;
if (i == 0)
queue[0] = e;
//如果只有一个元素之间放在堆顶
else
siftUp(i, e);
//否则调用siftUp函数从下往上调整堆。
return true;
}
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(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)
break;
queue[k] = e;//被比较的节点向父节点移动
k = parent;//保存当前位置
}
queue[k] = key;
}
poll() 和 remove() 方法
poll 方法每次从 PriorityQueue 的头部删除一个节点,也就是从小顶堆的堆顶删除一个节点,而remove()不仅可以删除头节点而且还可以用 remove(Object o) 来删除堆中的与给定对象相同的最先出现的对象。先看看poll()方法。下面是poll()之后堆的操作
删除元素后要对堆进行调整:
堆中每次删除只能删除头节点。也就是数组中的第一个节点。
将最后一个节点替代头节点然后进行调整。 。。。。。
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public E poll() {
if (size == 0)
return null;
//如果堆大小为0则返回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;
}
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; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
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;
}
如果当前节点比左右节点中的最小节点 小 就 break,否则一直与左右节点的最小节点交换。直到当前节点无子节点。
remove(Object o):
public boolean remove(Object o) {
int i = indexOf(o);
if (i == -1)
return false;
else {
removeAt(i);
return true;
}
}
//O(n)
private int indexOf(Object o) {
if (o != null) {
for (int i = 0; i < size; i++)
if (o.equals(queue[i]))
return i;
}
return -1;
}
private E removeAt(int i) {
// assert i >= 0 && i < size;
modCount++;
int s = --size;
if (s == i) // removed last element
queue[i] = null;
else {
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;
}
自测用例:
public class A_05_PriorityQueue {
public static void main(String[] args) {
KthLargest kthLargest = new KthLargest(3, new int[]{4, 5, 8, 2});
// System.out.println(kthLargest.add(3));
// System.out.println(kthLargest.add(5));
// System.out.println(kthLargest.add(10));
// System.out.println(kthLargest.add(9));
// System.out.println(kthLargest.add(4));
PriorityQueue<Integer> mQueue = new PriorityQueue<>(3);
mQueue.add(3);
mQueue.add(2);
mQueue.add(1);
System.out.println("remove obj=="+mQueue.remove(Integer.valueOf((2))));
// customMaxHeap();
}
private static void customMaxHeap() {
//是用 自定义的compare
PriorityQueue<People> queue = new PriorityQueue<People>(5,
new Comparator<People>() {
public int compare(People p1, People p2) {
// return p2.age - p1.age;//Max heap
return p1.age - p2.age;//Min heap
}
});
for (int i = 1; i <= 10; i++) {
// queue.add(new People("张"+ i, (new Random().nextInt(100))));
People people = new People("张" + i, (new Random().nextInt(100)));
queue.add(people);
System.out.println("add..."+people.toString());
}
System.out.println("***********************");
while (!queue.isEmpty()) {
System.out.println(queue.poll().toString());
}
}
}
class KthLargest {
//PriorityQueue默认是一个小顶堆,然而可以通过传入自定义的Comparator函数来实现大顶堆
private final PriorityQueue<Integer> queue;
private final int k;
public KthLargest(int k, int[] nums) {
this.k = k;
queue = new PriorityQueue<>(k);
for (int num:nums) {
add(num);
}
}
public int add(int val) {
if (queue.size()<k){
queue.add(val);
}else if (val>queue.peek()){
queue.poll();
queue.offer(val);
}
return queue.peek();
}
}
/**
* Your KthLargest object will be instantiated and called as such:
* KthLargest obj = new KthLargest(k, nums);
* int param_1 = obj.add(val);
*/
class People {
String name;
int age;
public People(String name, int age){
this.name = name;
this.age = age;
}
public String toString() {
return "姓名:"+name + " 年龄:" + age;
}
}
參考文章: