堆的应用比较多,比如堆排序等等,下面就来介绍下堆

堆是一棵树(完全二叉树)的形式,其每个结点都有一个值,且每个结点的值都大于/小于等于其父结点的值

  • 小根堆:每个结点的值都大于等于其父结点的值
  • 大根堆:每个结点的值都小于等于其父结点的值

注意:堆的根结点存放的是最大值或者最小值,但其他结点的值的排序是未知的,这与二叉搜索树不同

堆的基本操作

  • 插入一个数

    在末尾插入新的元素,然后向上调整

  • 求堆中的最小(大)值

    返回堆顶元素

  • 删除最小值

    用末尾的元素去覆盖堆顶元素,然后再将堆顶元素向下调整

  • 删除任意一个元素

    将任意一个元素覆盖堆顶元素,然后再将堆顶元素向下或者向上调整

  • 修改任意一个元素

    直接赋值,再将该元素向上或者向下调整

const int N = 100010;
int heap[N],l;

void down(int x) {
  //用t来代表根结点和其左右儿子中的最小值的下标
  int t = x;
  //如果左儿子存在且左儿子小于t,则t等于左儿子
  if (x * 2 <= l && heap[x * 2] < heap[t]) {
   	 t = x * 2; 
  }
  if (x * 2 + 1 <= l && heap[x * 2 + 1] < heap[t]) {
    t = x * 2 + 1;
  }
  //当根结点不是最小值的时候
  if (x != t) {
    swap(heap[t],heap[x]);
    down(t);
  }
}

void up(int x) {
  //当该点有父结点且该点的值大于其父结点的值,则进行交换
  while (x/2 > 0 && heap[x/2] > h[x]) {
    swap(heap[x/2],heap[x]);
    x /= 2;
  }
}

//最小值
heap[1];

//删除最小元素
heap[1] = heap[l];
l--;
down(1);

解释:为何删除元素时需要覆盖堆顶元素,再调整

由于我们使用数组储存堆,在数组中,删除第一个元素比较困难,但删除最后一个元素比较简单,我们只需要用最后一个元素覆盖掉第一个元素,这样一来,最后一个元素还保存在数组中,只是位置变了,而要删除的元素已经不在了,此时我们使数组的长度减一,再将第一个元素进行排序,调整到合适的位置即可

堆的储存方式

我们采用数组的方式去实现堆

下标从1开始时:

  • x的左儿子:2 * x
  • x的右儿子:2 * x + 1

堆的建立

我们在数组储存了所有的元素后,需要建堆,此时我们一般采用一种O(n)的方法来实现

以小根堆为例,我们从n/2处的元素开始执行向下调整(down)操作

for (int i = n/2;i > 0; i--) {
  down(i);
}

解释:因为n/2是堆的倒数第二层,所以我们将上面的所有元素都进行一个下沉操作来维护堆的有序

STL中的堆

c++中,提供了一个优先队列,实现堆的功能

priority_queue自动实现排序

//大根堆
priority_queue<int> heap;
//小根堆
priority_queue<int,vector<int>,greater<int>> heap;

//返回堆顶元素
heap.top();
//向堆插入一个数
heap.push(1);
//弹出堆顶元素
heap.pop();
//判断堆是否为空
heap.empty();
//返回元素个数
heap.size();
posted @ 2021-01-08 10:50  阿-栋  阅读(243)  评论(0编辑  收藏  举报