堆及其应用
堆是一个完全二叉树,分为大顶堆和小顶堆。大顶堆每一个节点的值都必须大于等于其子树中每个节点的值,小顶堆正好相反
既然堆是完全二叉树,那么就可以通过数组来构建堆,父节点下标为i/2,左子节点i×2,右子节点i×2+1实现快速定位。
插入调整
记住四个字:自下而上,下图是插入22的流程图:
以下是伪代码:
void insert(int data) {
if (count >= n) return; //堆满了
++count;
val[count] = data;
int i = count;
while (i/2 > 0 && val[i] > val[i/2]) { //自下往上
swap(i, i/2); //只要父节点比子节点小,就交换
i = i/2;
}
}
删除调整
堆只允许删除堆顶元素,删除记住四个字:自上而下,每次删除把最后一个元素覆盖到堆顶,然后个数--,最后维护堆。以下是删除堆顶元素33:
以下是伪代码:
void removeMax() {
if (count == 0) return; //堆空
val[1] = val[count]; //把最后一个元素覆盖到堆顶,然后count--
--count;
//维护大顶堆每个节点都比其子节点大的性质
int i = 1;
while (i <= n) {
int maxPos = i;
if (i*2 <= n && val[i] < val[i*2]) maxPos = i*2;
if (i*2+1 <= n && val[maxPos] < val[i*2+1]) maxPos = i*2+1;
if (maxPos == i) break; //若当前节点比子节点都打,则跳出自顶向下循环
swap(i, maxPos); //否则和最大的子节点交换,并继续自顶向下递归
i = maxPos;
}
}
应用
合并有序小文件
假设把100个有序小文件合并成一个有序大文件,我们先从每个文件中取第一个字符来构建大小为100的小顶堆,每次取出堆顶判断堆顶元素为几号文件,再从对应文件取下个字符放入堆中,重复过程直到排序完成,时间复杂度为O(nlogn)
高性能定时器
将任务按照任务执行时间存储在堆中,堆首存储最先执行的任务。系统拿堆顶执行时间点与当前时间相减得到间隔t,过t秒后定时器取堆顶任务执行并计算重新计算新堆顶的间隔,重复这个过程。
求topK大
我们维护一个容量为K的小顶堆,不满K时一直插入直到堆满。堆满后每次比较当前值和堆顶,若小于堆顶元素直接删除,大于堆顶则将堆顶元素替换为当前值并维护小顶堆的特性(即小顶堆中任意节点小于其子节点),重复这个过程,时间复杂度为O(nlogK)
求中位数
维护一个小顶堆和一个大顶堆,保证:
- 大顶堆中所有元素都小于小顶堆元素
- 0 <= 大顶堆个数 - 小顶堆个数 <= 1
这样大顶堆的堆顶元素即为中位数,时间复杂度为O(nlongn),过程演示: