小白学算法(15)堆

162 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第19天,点击查看活动详情

堆排序

堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点 --百度百科

1.插入一个数

2.求集合当中的最小值

3.删除最小值

4.删除任意一个元素

5.修改任意一个元素

STL里面的堆就是优先队列

堆是一棵完全二叉树

小根堆:左右子节点都大于根节点

大根堆:上者相反

存储方式:一维数组,根节点x,左节点2x,右节点2x+1

down(),up() 进行向下向上排序

1.模板介绍

1)初始化

// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
//size 是当前节点  (参考前面模拟单链表等)
int h[N], ph[N], hp[N], size;

2)交换

// 交换两个点,及其映射关系
void heap_swap(int a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

3)获得堆顶

//得到堆顶
int getTop()
{
   return h[1]; 
}

4)排序

void down(int u)
{
    int t = u;
    //u*2<=size 判断此时节点有左子节点
    //h[u*2]<h[t]  正常存储小根堆,那么父节点应该小于子节点
    if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
    //小根堆 左子节点应该小于右子节点
    if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    
    //如果此时t 临时节点不为当前节点,说明需要进行排序
    if (u != t)
    {
        //交换后 继续向下搜索排序
        heap_swap(u, t);
        down(t);
    }
}

void up(int u)
{
    //u/2父节点存在,并且父节点大于子节点
    while (u / 2 && h[u] < h[u / 2])
    {
        heap_swap(u, u / 2);
        
       // u/=2 向上移动
        u >>= 1;
    }
}

5)去掉堆顶

去除堆顶,因为使用一维数组进行存储堆,可以使堆顶为堆尾,然后去除堆尾,进行down操作,使得此时堆顶的堆尾复原

1 2 3 4 5 --> 5 2 3 4 5

5 2 3 4 --> down()进行排序,也可以用up(),只是down知道堆顶是1,up就不知道堆底了

//去除堆顶,因为使用一维数组进行存储堆,可以使堆顶为堆尾,然后去除堆尾,进行down操作,使得此时堆顶的堆尾复原
void Extract()
{
    h[1]=h[n--];
    down(1);
}

6)初始化堆

// O(n)建堆
//为什么从n/2开始    最大数是n,n/2就是它的父节点,那么每次n/2--,就会遍历整个n
for (int i = n / 2; i; i -- ) down(i);

2.实操

838. 堆排序 - AcWing题库

输入一个长度为 nn 的整数数列,从小到大输出前 mm 小的数。

输入格式

第一行包含整数 nn 和 mm。

第二行包含 nn 个整数,表示整数数列。

输出格式

共一行,包含 mm 个整数,表示整数数列中前 mm 小的数。

数据范围

1≤m≤n≤1051≤m≤n≤105,
1≤数列中元素≤1091≤数列中元素≤109

输入样例:

5 3
4 5 1 3 2

输出样例:

1 2 3
#include<iostream>
using namespace std;
const int N=1e6+10;
int h[N],Size;
void down(int x){...}
void remove(){...}
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>h[i];
    }
    Size=n;
    for(int i=n/2;i;i--)
    {
        down(i);
    }
    while(m--)
    {
        cout<<h[1]<<" ";
        remove();
    }
    return 0;
    
    
}

839. 模拟堆 - AcWing题库

维护一个集合,初始时集合为空,支持如下几种操作:

  1. I x,插入一个数 x;
  2. PM,输出当前集合中的最小值;
  3. DM,删除当前集合中的最小值(数据保证此时的最小值唯一);
  4. D k,删除第 k 个插入的数;
  5. C k x,修改第 k 个插入的数,将其变为 x;

现在要进行 N 次操作,对于所有第 2 个操作,输出当前集合的最小值。

输入格式

第一行包含整数 NN。

接下来 NN 行,每行包含一个操作指令,操作指令为 I xPMDMD kC k x 中的一种。

输出格式

对于每个输出指令 PM,输出一个结果,表示当前集合中的最小值。

每个结果占一行。

数据范围

1≤N≤1e5 −1e9≤x≤1e9 数据保证合法。

输入样例:

8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM

输出样例:

-10
6
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int h[N];   //堆
int ph[N];  //存放第k个插入点的下标
int hp[N];  //存放堆中点的插入次序
int Size;
void swap_1(int u,int v){}
void down(int x){}
void up(int x){}
int main()
{
    int n;
    //插入的次序
    int m=0;
    cin>>n;
    while(n--)
    {
        string op;
        int x,y;
        cin>>op;
        //插入
        if(op=="I"){
            cin>>x;
            m++;
            //先存储数据  c++ size变为关键字了
            h[++Size]=x;
            //存储当前数据的插入次序
            hp[Size]=m;
            //当前插入次序的下标
            ph[m]=Size;
            // 放入底端进行向上操作
            up(Size);
        }else if(op=="PM"){
            //输出顶端 h[[1]即可
            cout<<h[1]<<endl;
        }else if(op=="DM"){
            //删除顶端 看模板
            swap_1(1,Size);
            Size--;
            down(1);
        }else if(op=="D"){
            //输入是第x个插入的
            cin>>x;
            //得到在堆中的下标
            int u=ph[x];
            //进行交换 类似删除顶端的操作
            swap_1(u,Size);
            Size--;
            //因为不知道交换而来的是大数还是小数,所需要up,down各一次
            up(u);
            down(u);
        }else if(op=="C"){
            //第x个插入的  改为y
            cin>>x>>y;
            //ph[x]得到下标  h[ph[x]]=y 进行修改
            h[ph[x]]=y;
            //同上"D"操纵,不知道大小数,down up各一次
            down(ph[x]);
            up(ph[x]);
        }
    }
    return 0;
}