用PriorityBlockingQueue实现最小优先队列, 需要考虑以下几个方面: 扩容、入队、出队
PriorityBlockingQueue内部用数组实现了堆排序.
图在末尾, 代码看不明白就看图
一、堆排序
1.1 堆排序概念
最小堆是一种经过排序的完全二叉树, 其中任一非终端节点的数据值均不大于其左子节点和右子节点的值
**换句话说: **
- 1、数组来实现二叉树, 所以满足二叉树的特性
- 2、根元素是最小的元素, 父节点小于它的两个子节点
- 3、树中的元素是相对有序的
1.2 如何实现堆的相对有序
插入元素时, 插入到数组中的最后一个元素的后面, 然后与该节点的父节点比较大小, 如果插入的元素小于父节点元素, 那么与父节点交换位置, 然后插入元素交换到父节点位置时, 又与该节点的父节点比较, 直到大于父节点元素或者到达堆顶, 该过程叫做 上浮, 即插入时上浮.
下沉, 即移除元素时下沉.
二、PriorityBlockingQueue入队操作
2.1 PriorityBlockingQueue.offer
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
int n, cap;
Object[] array;
// 1.入队时, 先判断是否需要进行扩容
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap);
try {
Comparator<? super E> cmp = comparator;
if (cmp == null)
// 2.数据入队
siftUpComparable(n, e, array);
else
siftUpUsingComparator(n, e, array, cmp);
// 3.size记录当前数组中有效元素的个数
size = n + 1;
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
1、入队之前先判断当前数组是否需要扩容
2、size记录的是数组中元素的个数
2.2 PriorityBlockingQueue.siftUpComparable
private static <T> void siftUpComparable(int k, T x, Object[] array) {
Comparable<? super T> key = (Comparable<? super T>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = array[parent];
// compareTo由x实现, 如果计算方式是return x.priority - e.priority, 则为最小堆
// 即优先级值最小的先出队
if (key.compareTo((T) e) >= 0)
break;
array[k] = e;
k = parent;
}
array[k] = key;
}
这里的计算方式: 先将要入队的元素插入到数组末尾, 然后获取元素的父节点的索引值, 父节点的定义规则是当前元素在数组中的索引值(index)-1 >>> 1, 入队元素与其父节点进行比较, compareTo < 0, 则将父子节点进行替换, 顺序交换完成之后, 继续按这种方式向上遍历比较.
备注: **新入队元素只与其父节点进行比较, 并不与其他节点比较, 所以该数组不是有序数组.
三、出队
3.1 dequeue
private E dequeue() {
int n = size - 1;
if (n < 0)
return null;
else {
Object[] array = queue;
E result = (E) array[0];
E x = (E) array[n];
array[n] = null;
Comparator<? super E> cmp = comparator;
if (cmp == null)
// x: 数组最后一个元素
siftDownComparable(0, x, array, n);
else
siftDownUsingComparator(0, x, array, n, cmp);
size = n;
return result;
}
}
3.2 siftDownComparable
// 1.T x对应数组中最后一个元素
private static <T> void siftDownComparable(int k, T x, Object[] array,
int n) {
if (n > 0) {
Comparable<? super T> key = (Comparable<? super T>)x;
// 2.初始化half为x元素的父节点
int half = n >>> 1;
while (k < half) {
// 3.取当前节点的左子节点(第一次为根节点)
int child = (k << 1) + 1;
// 4.当前节点的左子节点的值赋值给Object c
Object c = array[child];
// 5.取右子节点的索引值
int right = child + 1;
if (right < n &&
((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
// 6.如果左子节点 > 右子节点, 右子节点的值赋值给Object c
c = array[child = right];
if (key.compareTo((T) c) <= 0)
// 7.如果最后一个节点的值 < 左右子节点的值, 操作结束, 直接return
break;
// 8.最后一个元素移动到堆顶后, 与其子节点中较小者进行比较
array[k] = c;
// 9.继续向下遍历
k = child;
}
// 10.遍历结束, 将最后一个元素的值放到指定的位置
array[k] = key;
}
}
单纯的看代码, 看文字很懵逼, 画个图方便日后回顾
四、流程图
在看下图之前, 需要明白一件事, 数组结构才是PriorityBlockingQueue中元素的真实结构, 二叉树只是该数组的映射, 画出数组的二叉树映射结构图, 是为了方便理解
4.1 数组实现最小堆的结构图
五、入队流程图
**结合上图可知: **用数组实现最小堆, 虽然出队是有序, 最小值先出队, 但是数组本身并不是有序数组
向数组中插入值为1的元素
5.1 val(1)对应index(13)
将val(1)插入到数组的末尾, 对应index = 13
5.2 val(1)与index(6)比较
父节点索引值parentIndex = 6, 对应val(12), 将val(1)与val(12)进行比较, 发现val(1)比val(12)小, 将val(1)与val(12)的位置进行置换, 置换以后val(1)对应index(6)
置换后如下图:5.3 val(1)与index(2)比较
val(1)与val(12)交换位置以后, val(1)对应index = 6, 其父节点parentIndex = 2, 即val(10), 此时val(1) < val(10), 所以val(1)与val(10)进行位置互换, 置换以后val(1)对应index(2)
置换后如下图:5.4 val(1)与index(0)比较
val(1)与val(10)交换位置以后, 其索引值对应index = 2, 其父节点parentIndex = 0, 即节点val(2), 然后val(1)与val(2)进行比较, val(1) < val(2), 所以val(1)与val(2)交换位 置, 互换以后val(1)对应index(0)
置换后如下图:
5.5 val(1)入队完成
六、出队流程图
根节点的元素始终是最小元素, 而且对应index(0), 出队取index(0)元素, 然后将数值末尾元素放置index(0)处, 然后依次与其子节点进行比较
6.1 取index(0)元素
6.2 val(12)元素放置index(0)处
将数值末尾元素放置index(0)的位置, 数组最后一个元素val = 12, 也就是将val(12)从index(13)移至根节点index(0)处
移动后如下图:6.3 val(12)元素与index(1)、index(2)比较
index(0)其左右子节点分别为childIndex(1)、childIndex(2), 对应的值分别为val(3)、val(2), val(3) > val(2), 所以讲val(12)与其右子节点childIndex(2)进行比较, val(12) > val(2), 将val(12)与val(2)进行置换, 置换后其index = 2
置换后如下图:
6.4 val(12)元素与index(5)、index(6)比较
index(2)其左右子节点分别为childIndex(5)、childIndex(6), 对应的值分别为val(11)、val(10), val(11) > val(10), 所以讲val(12)与其右子节点childIndex(10)进行比较, val(12) > val(10), 将val(12)与val(10)进行置换, 置换后其index = 6
置换后如下图:
6.5 出队结束
完成一次出队操作之后, 当前数组结构如下图所示