PriorityQueue是优先队列,能根据优先级,依次取出元素,它是由二叉堆实现的,同时二叉堆也是实现优先队列的经典数据结构,它是一颗完全的二叉树,所有的叶子节点都在同一层,有2种实现,最大堆,每个节点都要大于它的孩子节点,最小堆反之。下面我们用图,来演示下自底向上构造堆,ACEJHX顺序插入。
由图可以看出,每次插入都是把元素放到树的最后,然后沿着父节点依次调整,直到不大于它的父节点为止。由于每次调整的都不超过树的高度,故时间复杂度为logn。
而取出操作也是相似的调整,只需把最后一个元素放到根处,然后依次与子节点比较,如果子节点大于自己,则进行交换,直到子节点全部小于自己位置,时间复杂度同样不超过树的高度logn。而堆排序就是n个元素组成堆,然后依次取出的过程,时间复杂度为nlogn。
现在基本的原理已经清楚了,我们看看java中如何实现堆的。看下构造函数:
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
发现java中是有数组来实现堆的(默认是最小堆),因为堆是完全二叉树,我们可以很容易用用数组来实现,只要定义:节点位置为i,那么左孩子位置在2i +1处,右孩子在2i+2处,而孩子节点的父节点则为(k-1)/2,我们来看看添加操作:
public boolean offer(E e) {
int i = size;//数组从0开始,size处就是要插入的新元素
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
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;
}
接下来,我们看看获取最高优先级的元素操作,也是移除操作:
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;
}
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;
}
需要注意的是:对于优先集合遍历的操作是按照数组先后顺序提取元素的,并非优先级。
最后演示下使用:
public class SimpleJava{
public static void main(String[] args) {
PriorityQueue<Integer> q = new PriorityQueue<Integer>();
q.add(1);
q.add(3);
q.add(4);
q.add(2);
int length = q.size();
for(int i = 0; i < length; i++)
System.out.println(q.poll());
}
}
打印:
1
2
3
4