[toc]
前言
PriorityQueue,即优先队列,队列中的元素依据某种规则进行排序,包括自然排序和定制排序。优先队列不允许插入 null,如果要使用自然排序,那么存储的对象必须实现 Comparable 接口。
本文将分析优先队列的构造、入队、出队等操作,其中又涉及到了扩容、上浮调整、下沉调整等。
以下代码基于 JDK 1.8
主要成员变量
1 | // 默认初始容量 |
构造方法
PriorityQueue 的构造方法有多个,主要分两类,一类需要传入集合,另一类不需要传入集合。
传入集合时如果原来的元素还没有排序,例如传入 List,则要重新排序,如果传入的是 TreeSet,就不需要重新排序了。
主要看另一类,另一类就比较简单,只是初始化了初始容量和 Comparator。
1 | public PriorityQueue() { |
入队
下面看一下入队,即添加元素的操作。入队主要有两个方法:add 和 offer。其中 add 方法调用的是 offer 方法:1
2
3public boolean add(E e) {
return offer(e);
}
所以主要看 offer 方法:
offer
1 | public boolean offer(E e) { |
该方法还是比较容易看懂的,主要就是先判断是否要扩容,然后将元素插入到合适位置。
下面分别看下其中的 grow 和 siftUp 方法:
grow
1 | private void grow(int minCapacity) { |
该方法用于扩容,对于新容量,如果旧容量小于 64,新容量为旧容量的两倍加 2,即插入新元素后还有一半空间,否则新容量比旧容量增加百分之五十。
siftUp
1 | private void siftUp(int k, E x) { |
该方法的第一个参数为要填充的位置,第二个参数为插入的对象。根据不同排序使用不同方法,不过这两个方法大同小异,这里只分析自然排序的情况,即 siftUpComparable 方法:
1 | private void siftUpComparable(int k, E x) { |
可以看到,插入节点一开始插入到最后,然后进行上浮,回归到正确位置。另外,这是一个小顶堆,即最小的元素在堆顶(queue[0])。
出队
看完入队操作,现在来看下出队操作。出队操作主要有两个方法:peek 和 poll。
其中 peek 方法获取但不删除队头元素。如果此队列为空,则返回 null。poll 方法获取并删除队头元素。如果此队列为空,则返回 null。
下面分别看下这两个方法:
peek
1 | public E peek() { |
这个方法很简单,不多说了。下面看 poll 方法:
poll
1 | public E poll() { |
该方法也是返回队头元素,不过多了一步删除队头元素的操作。删除队头元素时,将原来的队尾元素放到队头,然后删除队尾元素,并对新的队头进行下沉调整。下沉调整用到了 siftDown 方法:
siftDown
1 | private void siftDown(int k, E x) { |
该方法和 siftUp 方法类似,第一个参数为插入位置,第二个参数为插入元素。然后从这个位置开始进行下沉操作。以自然排序为例,看 siftDownComparable 方法:
1 | private void siftDownComparable(int k, E x) { |
下沉的时候和上浮相反,和较小的子节点交换位置,直到下沉到正确位置。
小结
- 优先队列支持自然排序和定制排序,自然排序需要对象实现 Comparable 接口,定制排序需要传入 Comparator。
- 不能添加 null。
- 优先队列基于小顶堆,用数组存储元素,达到数组容量时进行扩容,在元素个数小于 64 时增加一倍,之后增加 0.5 倍。
- 添加元素到队尾以及删除队头元素时,都要进行调整。添加元素到队尾时,该元素进行上浮调整,上浮到合适的位置。删除队头元素时,将队尾元素移动到队头,并删除队尾元素,然后对新的队头元素进行下沉调整,下沉到合适的位置。