堆--学习笔记(acwing)

一:定义以及部分性质

1:堆中某个节点的值总是不大于或不小于其父节点的值;

2:堆总是一棵完全二叉树。

3:最后一个非叶结点是第n/2个结点。这里建堆的时候会用到,复杂度可以降到O(n);

二:关于建堆

如果一个点为u,那么它的左儿子为2*u,右儿子为2*u+1

对于小根堆来讲,每个以u为根节点的树,它的左右儿子的值一定大于父节点。

所以对堆进行建立的时候,如果当前点与它的左右儿子并不满足这一性质,就需要进行交换。

对于建堆的过程:

由(一)第三条,我们建堆的时候,只需要从n/2开始,到1即可。因为已知最后一个非叶结点是n/2,那么>n/2的结点,都是叶子结点,它本身所构成的子树只有它本身。一定符合小根堆性质,所以是没必要看的。我们从第n/2结点开始,每次让它向下比较一次,变为局部的合法(比如第一次只有三个点,满足父节点都小于左右儿子结点)。然后它的向上的父节点再向下比较,那么每次比较,都会得到一个局部合法的子树(本次进行的父节点往下的子树)。所有的n/2~1都这么操作,一定可以把整个完全二叉树变成合法的小根堆。

有:

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

对于向下比较的过程,我们定为down(u)函数。由小根堆的性质,u为父节点,那么它的左右儿子h[2*u]和h[2*u+1]都要大于它,否则就要进行交换:

void down(int u)
{
    //t表示三个点中的最小值 
    int t=u;
    //判断是否有左儿子
    if(u*2<=siz&&h[2*u]<h[t]) 
    {
        t=2*u;
    }
//是否有右儿子
if(2*u+1<=siz&&h[2*u+1]<h[t]) { t=2*u+1; } if(u!=t) {
     //需要交换,则交换,继续down往下判断 swap(h[u],h[t]); down(t); } }

只要down()就可以把堆给建好了。

关于up操作:

比down更简单,只需要与它的父节点u/2进行比较即可,非法即交换。

void up(int u)
{
    while(u/2>=1&&h[u/2]>h[u])
    {
        swap(u,u/2);
        u=u/2;
    }
}

三:堆的操作

定siz为树大小

1:插入一个数:

直接在末尾加一个,所以++size处插入新数,然后进行更新树。由于此时它处于树的最底部,所以直接up(size)即可。

2:求集合中最小值:

h[1]

3:删除最小值:

把树尾覆盖上去,siz--,由于此时它处于树的顶部,所以直接down(1)即可。

4:删除任意一个元素:

删除K号结点。与3类似,直接把树尾覆盖上去,siz--,但是需要up(k)或down(k)一遍,为了简便,把up和down都写上,只会执行其中一个。

5:修改任意一个元素

直接修改h[k]=x,up(k),down(k)

四:堆排序

堆的其中一个作用,堆排序:时间复杂度O(NlogN)

板子题:https://www.acwing.com/problem/content/description/840/

#include<cstdio>
#include<cstring>
#include<vector>
#include<set>
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=1e5+10,maxn2=31*maxn;
int n,m;
int h[maxn],siz;//1~n
void down(int u)
{
    //t表示三个点中的最小值 
    int t=u;
    //判断是否有左儿子
    if(u*2<=siz&&h[2*u]<h[t]) 
    {
        t=2*u;
    }
    if(2*u+1<=siz&&h[2*u+1]<h[t])
    {
        t=2*u+1;
    }
    if(u!=t)
    {
        swap(h[u],h[t]);
        down(t);
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>h[i];
    }
    siz=n;
    for(int i=n/2;i>=1;i--)
    {
        down(i);
    }
    while(m--)
    {
        cout<<h[1]<<" ";
        h[1]=h[siz]; //更新最小值
        siz--;
        down(1);
    }
    return 0;
}

vector排序版:

#include<cstdio>
#include<cstring>
#include<vector>
#include<set>
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=2e5+10,maxn2=31*maxn;
const int inf=1e18+10;
ll a[maxn],b[maxn];
int main()
{
    int n,m;
    vector<int>v;
    cin>>n>>m;
    int x;
    for(int i=1;i<=n;i++)
    {
        cin>>x;
        v.push_back(x);
    }
    sort(v.begin(),v.end());
    for(int i=0;i<m;i++)
        cout<<v[i]<<" ";
        cout<<endl;
    return 0;
}

五:堆操作的基本实现:

地址:https://www.cnblogs.com/liyexin/p/13942227.html

原文地址:https://www.cnblogs.com/liyexin/p/13942174.html