JDK-In-Action-PriorityQueue
优先级队列
优先级队列介绍
基于优先级堆的无界优先级队列。优先队列的元素根据它们的自然顺序排序,或者根据使用的构造函数由队列构建时提供的比较器排序。优先队列不允许空元素。依赖于自然排序的优先级队列也不允许插入不可比较的对象(这样做可能会导致ClassCastException)。相对于指定的顺序,此队列的头是最小的元素。如果多个元素为最小值绑定,则head是这些元素之一——绑定是任意断开的。队列检索操作轮询、删除、查看和元素访问队列头部的元素。优先队列是无界的,但是有一个内部容量来控制用于在队列中存储元素的数组的大小。它总是至少与队列大小一样大。当元素被添加到优先队列时,它的容量会自动增长。增长策略的细节没有指定。该类及其迭代器实现集合和迭代器接口的所有可选方法。方法Iterator()中提供的迭代器不能保证以任何特定的顺序遍历优先队列的元素。如果需要有序遍历,可以考虑使用array.sort(pq.toArray())。注意,这个实现不是同步的。如果任何线程修改队列,多线程不应该并发地访问PriorityQueue实例。相反,应该使用线程安全的java.util.concurrent.PriorityBlockingQueue
类。实现说明:此实现为入队和出队方法(offer,poll,remove()和add)提供O(log(n))时间;remove(Object)和contains(Object)方法的线性时间;以及检索方法(peek, element和size)的常数时间。该类是Java集合框架的成员。
数据结构
使用线性表来存储平衡二叉最小堆
transient Object[] queue;
基于线性表的算法设计:
最后一个非叶子节点索引: lastNonLeafIndex= size <<< 1 - 1
判断有无子节点: hasChild(n) = n < ( size << 2 )
求子节点索引: leftChildIndex=n*2+1, rightChildIndex=leftChildIndex+1,注意判断索引越界
算法图解
初始数组值(未构造最小堆): [9,8,7,6,4,3,2,1]
初始二叉堆的图形如下
平衡二叉最小堆的构造
首先找到最大的非叶子节点ID,与它的两个子节点比较,将其中的较小值和它交换,如果它最小的子节点还有子节点,跳到较小节点的位置,重复该过程,否则退出.
第二步: 移动到倒数第二个非叶子节点,重复第一步的过程.
第三步: 继续到下一个非叶子节点,直到根节点,重复前两步的过程
节点1和节点3交换值,因为节点数据发生交换,且节点3还有子节点,所以需要继续处理节点3;
如果未发生节点值交换,可以不用继续处理子节点;
交换节点3和节点7的值,节点7没有子节点了,退出本轮循环.
处理下一个非叶子节点0,交换节点0和节点1的值.
同理,继续交换节点1和节点3的值.
同理,继续交换节点3和节点8的值,到此,平衡二叉最小堆构建完成
节点新增算法
节点新增的算法步骤:
- 在堆的最后新增一个叶子节点,这一步可能需要扩容(见后文如何JDK的扩容策略);
- 和它的父节点比较,如小于等于父节点,则交换值,否则插入完成;
- 跳到父节点,因为发生了值交换,所以需要和当前节点的父节点再进行交换比较,重复该过程,直到根节点;
-
若新增值为3
-
交换4,5节点的值
-
因为节点4大于父节点,故操作完成
根节点移除
算法步骤:
- 将最后一个节点和根节点交换.
2.以根节点未起点,向下交换较小的节点.直到无子节点或比子节点都小.
-
移出根节点值,将节点9的值赋值给根节点, 节点9的值赋值null
-
节点1值小于根节点,交换值, 因为有子节点,且存在更小的值,需要继续该过程
-
交换节点1和节点4的值,操作完成
节点值索引查询
算法步骤:
1.从0到size-1,一次比较数组值和目标值,返回相等时的索引
节点删除
算法步骤:
- 交换删除节点和尾节点的值;
- 由于发生了值交换,需要应用下沉或上浮来确保树的性质;
-
情况1: 替换值后发生下沉操作
-
情况2: 替换值后发生上浮操作
-
情况三: 即没有上浮也没有下沉
迭代器
迭代器next()会导致遍历指针+1, remove()会移除最后一次迭代的元素;
在迭代的过程中如果调用 remove()会由于上浮下沉出现
- 未访问的尾元素被移动到已经访问的位置(节点删除情况3)或
- 其他未访问的位置(节点删除情况2)或
- 当前删除的元素位置(节点删除情况3)三种情况.
所以为了能完整的遍历出所有的元素,对于情况1和情况3,需要让next的指针减1来访问尾节点或被下沉中交换上来的元素;对于情况2,需要保存尾节点元素,最后再访问这些漏掉的元素;
扩容
扩容前容量小于64:容量翻倍
否则: 增长50%
扩容后检查最大容量限制: Integer.MAX_VALUE - 8 或者 Integer.MAX_VALUE
/**
* Increases the capacity of the array.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
API Example
构造函数之其他优先级队列参数
PriorityQueue<Integer> argQueue = new PriorityQueue<>(Arrays.asList(1, 3, 5));
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(argQueue);
assertEquals(priorityQueue.toArray(), "[1, 3, 5]");
构造函数之带初始容量和比较器
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(2, (o1, o2) -> o2 - o1);
assertEquals(priorityQueue.toArray(), "[]");
构造函数之最大堆
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(((o1, o2) -> o2 - o1));
priorityQueue.addAll(Arrays.asList(1, 3, 5));
assertEquals(priorityQueue.toArray(), "[5, 1, 3]");
构造函数之有序集参数
SortedSet<Integer> set = new TreeSet<>(Arrays.asList(1, 3, 5));
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(set);
assertEquals(priorityQueue.toArray(), "[1, 3, 5]");
构造函数之集合参数
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 3, 5));
assertEquals(priorityQueue.toArray(), "[1, 3, 5]");
构造函数之默认参数
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
assertTrue(priorityQueue.size() == 0);
assertEquals(priorityQueue.toArray(), "[]");
元素删除 Remove Poll
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));
boolean removeSuccess = priorityQueue.remove(2);
assertEquals(priorityQueue.toArray(), "[1, 3, 4, 6, 3, 5]");
//移除堆顶元素1,底层调用的是poll(),但是如果堆为空,该方法抛出异常
priorityQueue.remove();
assertEquals(priorityQueue.toArray(), "[3, 3, 4, 6, 5]");
//移除堆顶元素3,堆为空时,返回null
Integer ele = priorityQueue.poll();
//ele may be null
assertEquals(priorityQueue.toArray(), "[3, 5, 4, 6]");
元素新增 Add Offer
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 3, 5));
//add()方法也是调用的offer()
priorityQueue.add(2);
assertEquals(priorityQueue.toArray(), "[1, 2, 5, 3]");
priorityQueue.addAll(Arrays.asList(3, 4));
assertEquals(priorityQueue.toArray(), "[1, 2, 4, 3, 3, 5]");
priorityQueue.offer(6);
assertEquals(priorityQueue.toArray(), "[1, 2, 4, 3, 3, 5, 6]");
元素查找 Contains
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));
//查询算法时间复杂度为O(lgN)
boolean contains = priorityQueue.contains(3);
assertTrue(contains);
堆顶值读取 Element Peek
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));
//算法时间复杂度为O(l)
//底层调用时peek(),但是空堆时,抛出异常
Integer element = priorityQueue.element();
assertEquals(element, 1);
//若队列为空,peek()返回值为空
element = priorityQueue.peek();
assertEquals(element, 1);
遍历队列之 Foreach无序遍历
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));
priorityQueue.forEach(e -> System.out.println(e));
1
2
4
3
3
5
6
遍历队列之 Iterator无序遍历
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));
Iterator<Integer> iterator = priorityQueue.iterator();
while (iterator.hasNext()) {
Integer value = iterator.next();
System.out.println(value);
}
1
2
4
3
3
5
6
遍历队列之有序遍历
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));
while (!priorityQueue.isEmpty()) {
Integer value = priorityQueue.poll();
System.out.println(value);
}
1
2
3
3
4
5
6
集合操作之交集 Retain
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));
priorityQueue.retainAll(Arrays.asList(1, 2, 3));
assertEquals(priorityQueue.toArray(), "[1, 2, 3, 3]");
引用
- 示例代码
java.util.PriorityQueue
Sorce Code