PriorityQueue (大根堆/小根堆/TopK问题)

1,861 阅读2分钟

背景

所谓TopK问题,即给定数据集,取最大或最小K条数据。通常来说都使用大根堆或小根堆解决,时间复杂度为nlog(k)。PriorityQueue 则是JDK提供的大根堆/小根堆实现

大根堆是棵二叉树,且根元素大于左右子树的所有元素,小根堆则相反(另外需要注意的是,*根堆和平衡二叉树的是两种算法)

PriorityQueue 中使用数组作为载体,基于数组使用二叉树的"视图"实现了log(k)的时间复杂度。

以下仅讨论大根堆


关键点

存储结构

PriorityQueue 以数组为存储结构,通过对数组下标的进行解释,创建了一个二叉树的视图,图示如下

数组下标

可能切换成二进制会更好理解一些

二进制

操作-新增

核心代码在方法siftUp

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;

这里k是数组下标,而其中仅使用 (k - 1) >>> 1 即完成了对数组的二叉树视图遍历

整个流程为,新增元素到数组末尾(可能存在扩容),根据二叉树视图,向上查找,如果大于根节点,则和根节点调换位置,直到根节点大于新增的元素为止。

操作-取根

核心代码在方法siftDown

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;

这里x是末尾节点,k为0。half表示下标最小的叶子节点。从根节点开始,拿左右子树的值与末尾的节点比较。如果小于,则末尾节点晋升为当前子树的根。否则,取左右子树较小的树;再重复上述操作。