数据结构与算法--堆

201 阅读3分钟

堆及其应用

​ 堆是一个完全二叉树,分为大顶堆和小顶堆。大顶堆每一个节点的值都必须大于等于其子树中每个节点的值,小顶堆正好相反

​ 既然堆是完全二叉树,那么就可以通过数组来构建堆,父节点下标为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)

求中位数

​ 维护一个小顶堆和一个大顶堆,保证:

  1. 大顶堆中所有元素都小于小顶堆元素
  2. 0 <= 大顶堆个数 - 小顶堆个数 <= 1

这样大顶堆的堆顶元素即为中位数,时间复杂度为O(nlongn),过程演示: