初步了解二叉堆(二叉堆及其基本操作)

参考资料:《算法竞赛进阶指南》- 李煜东

一.什么是二叉堆

如图,简单来说,二叉堆是一棵满足“堆性质”的完全二叉树,树上的每一个节点都带有一个权值。
若树中任意的一个节点的权值都小于等于其父节点的权值,则称满足该性质的完全二叉树大根堆(根权值最大)。
若树中任意的一个节点的权值都大于等于其父节点的权值,则称满足该性质的完全二叉树小根堆(根权值最小)。

二叉树是一种支持插入、删除、查询的数据结构。

二.二叉堆的基本操作

在介绍二叉堆的基本操作之前,我们要先说说如何建堆。

根据完全二叉树的性质,我们可以采用层次序列存储方式,直接用一个数组来保存二叉堆。我们让父节点的编号等于子节点编号除以 2 ,左子节点编号等于父节点编号乘以 2 ,右子节点编号等于父节点编号乘以 2 加 1 。(如上图所示)

接下来,我们以一个大根堆为例,介绍一下二叉堆的基本操作

1.维护二叉堆(Down/Up)

为了保证大根堆满足它的性质,我们有 Down 和 Up 两种操作。
Down 是从某个节点从上往下维护,在某个节点中,如果它的左儿子或右儿子是三个节点中最大的,则应该让最大的与父节点交换。

void down(int p)
{
  int t=p*2;//左儿子
  while(p<=n)
  {
    if(t<n&&heap[p]<heap[p+1])//取子节点中最大的
        t++;
    if(heap[t]>heap[p])
    {
      swap(heap[t],heap[p]);
      p=t,t=2*p;//p到了t的位置,继续循环
    }
    else 
        break;
  }
}

Up 同理,是从下向上维护

void down(int p)
{
  while(p>1)
  {
    if(heap[p/2]<heap[p])
    {
      swap(heap[p/2],heap[p])
      p/=2;
    }
    else 
        break;
  }
}

2.插入一个数(Insert)

如果要向二叉堆中插入一个权值为 val 的点,我们可以直接将它放入堆末尾,然后 Up 维护

void insert(int val)
{
    heap[++n]=val;
    up(n);
}

3.求最大值(GetTop)

int GetTop()
{
  return heap[1];
}

4.删除最大值(Extrat)

即删除堆顶,我们可以将堆顶和堆尾交换,后删除堆尾,然后 Down 操作

void Extrat()
{
   heap[1]=heap[n--];
   down(1);
}

5.删除任意一个元素(Remove)

如果我们要删除 k 元素,可以将它与堆尾交换,后删除堆尾,然后进行 Down 和 Up两个操作(因为被换到中间,向上向下调整都有可能)

void remove(int k)
{
   heap[k]=heap[n--];
   up(k),down(k);
}

6.修改任意一个元素(Change)

void change(int k,int x)
{
   heap[k]=x;
   up(k),down(k);
}

三.例题(AcWing 838 堆排序)

原题

这题就运用到了上面四个基本操作,值得注意的是,我们开始应该如何建堆?我们可以对 n/2 到 1 的节点进行 Down 操作(因为叶节点没有子节点,就不用枚举了),也可以从 2 到 n 进行 Up 操作,这样我们可以在 O(n) 时间内维护一个二叉堆。

时间复杂度的证明是一个等差比数列的求和,这个和小于1,所以为 O(n)。

应注意的是,这题是维护一个小根堆,所以上面操作应该有所修改。

代码:

#include<bits/stdc++.h>
using namespace std;
int heap[100005],n,x,m,a[1000005];
void down(int p)
{
  int t=p*2;
  while(t<=n)
  {
    if(t<n&&heap[t]>heap[t+1])
        t=t+1;
    if(heap[t]<heap[p])
    {
        swap(heap[t],heap[p]);
        p=t,t=p*2;
    }
    else
        break;
  }

}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
      cin>>heap[i];
    }
    for(int i=n/2;i;i--)
    {
      down(i);
    }
    while(m--)
    {
      printf("%d ",heap[1]);//每次取出堆顶
      heap[1]=heap[n--];
      down(1);
    }
return 0;
}

四.STL 优先队列

可以说优先队列和二叉堆有着相同的性质,而在C++中由于priority_queue的存在,为我们上述的几个操作提供了极大的便利,有现成的函数可以拿来使用,所以下面就来简单介绍一下。

头文件:#include<queue>

该STL语法除了大多数STL支持的 heap.size() , heap.empty() 之外,还有以下几个

1.建立优先队列

priority_queue<int>heap;//默认为大根堆

如果想要实现一个小根堆,有两种方法:

① 直接在插入元素时以相反数插入,这样就实现了越“小”的数越在上面,记得取出后要还原

② 建优先队列时做修改

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

2.把元素插入 O(logn)

heap.push(x);

3.删除堆顶元素 O(logn)

heap.pop();

4.查询堆顶元素 O(1)

int x=heap.top();

注意:queue 没有 heap.clear() 操作

posted @ 2020-04-03 01:51  Pecoz  阅读(932)  评论(0编辑  收藏  举报