一、数据结构
二叉堆
首先,二叉堆是一颗被完全填满的二叉树。
- 有任意节点的左节点为2i+1,右节点为2i+2;
- 在一个堆中,对于每一个节点X,X的父亲中关键字小于(或等于)X中的关键字,根节点除外。
- insert操作:上滤,从下往上寻找可以插入的节点;
- delete操作:下滤,删除根,递归根节点的最小儿子补位。
- 定理:包含2^(k+1)-1个节点,高为h的理想二叉树的节点的高度和为2^(k+1)-1-(h+1);
d-堆
d-堆是二叉堆的简单推广,它就像一个二叉堆,只是所有节点都有d个儿子,二叉堆是2-堆。
数据显示,4-堆可以胜过二叉堆。
左式堆
被设计用来有效的支持合并操作。
在左式堆中,对于每一个节点X,做儿子的零路径长至少与右儿子的零路径长相等;
零路径长:任一节点X的零路径长是从X到一个不具有两个儿子的节点的最短路径长;
如何合并:
- 对于两个左式堆,首先递归的将具有大的根植B的堆与具有小的根植S的右子堆合并;
- 将合并后的堆放到S堆的右节点;
- 交换S堆的左右节点更新零路径长。
斜堆
是左式堆的自调节形式,斜堆不保留零路径长的概念。
二项队列
二项树Bk由一个带有儿子的B0,B1,B2...B(k-1)的根组成。与二进制一一对应,如大小为13的优先队列可以用B3,B2,B0表示,记为1101。
合并:
插入:是特殊情形的合并。
删除:
二、优先队列的应用
- 给定一个无序集合和一个整数k,找出第k个最大的元素;
-
- 构建二叉堆,删除k次,即为所求;
三、标准库中的优先队列
PriorityQueue
/**
* 插入核心方法,使用二叉堆实现,上滤
* @param k
* @param x
*/
@SuppressWarnings("unchecked")
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;
}
/**
* 删除核心方法,删除根节点后,拿出最后一个节点,
* 循环将根节点中较小的儿子节点上移补位,最后将最后一个节点放到空出的位置
* @param k
* @param x
*/
@SuppressWarnings("unchecked")
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) {
// 二叉堆性质,左节点=2i+1
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;
}
参考文献:
- 图解二项队列,以及复杂度分析
- 《数据结构预算法分析描述》