模拟堆(详解+例题)

一、定义

维护一个数据集合,堆是一个完全二叉树。


那么什么是二叉树呢?

如图:ebb910b87cae4c698f49a9eb76fc14b5.png


二、关于小根堆实现

98d6a4f12d914ceabbdd0012629c2fa2.png

性质:每个根节点都小于等于左右两边,所以树根为最小值。 


2.1、堆存储(用一维数组来存)

bf65d851a0664d35a8bcf62691257dba.png

 记住规则:x(根)的左儿子 = x * 2;

                   x(根)的右儿子 = x * 2 + 1

样例如图: 

dfdb5dd1f8594749948cda228def91ab.png


2.2、操作

2.2.1、操作1、down(x) :节点下移

如果把一个值变大了,就让他往下移动。

样例: 

471bee5173a740a7a7e122f132bdfed7.png


2.2.2、操作2、 up(x):节点向上移

如果把某一个值变小了,就让他向上移动

样例: 

bdb1d592caa5409288493505bae3bb3d.png


2.3、如何手写一个堆?

注意:下标要从1开始,heap[]堆,size:堆长度

如图: 

849e092dacfd4f888d45cb9a524c68a7.png

这里后面会有对应的例题,详细解释4、5操作 


 三、例题:

3.1、堆排序 :此题来源于acwing

5a2c41393a6c4e7faa4086f53a10ad1a.png


 图解:

70236ccf14a34959a435887e82dc5a3e.png

1d2e13b0cde0413595169d7977eee8ad.png 上述两个图来自于B站董晓算法的视频


代码模板: 
//下沉
void down(int u)
{
    int t = u;
    if(u*2 <= sz && h[u*2] < h[t]) t = u * 2;
    if(u*2+1 <= sz && h[u*2+1] < h[t]) t = u * 2 + 1;
    if(t != u)
    {
        heap_swap(t,u); //由于后面需要找到第k的数,所以不能用普通的交换,需要用我们手写的交换函数
        down(t);//下沉,直到不满足位置
    }
}
//上浮
void up(int u)
{
    //与根部比较,如果父亲大于儿子就需要上浮
    while(u/2 && h[u/2] > h[u]){
        heap_swap(u/2, u);
        u = u/2;
    }
}

AC代码:
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1e5+10;
int h[N],sz;
int n,m;

void down(int u)
{
    int t = u;
    if(u*2 <= sz && h[u*2] < h[t]) t = u * 2;
    if(u*2+1 <= sz && h[u*2+1] < h[t]) t = u * 2 + 1;
    if(t != u)
    {
        swap(h[t],h[u]);
        down(t);
    }
}

int main()
{
    scanf("%d %d", &n, &m);
    for(int i=1;i<=n;i++) scanf("%d ",&h[i]);
    sz = n;
    //构建堆,从n/2序号开始创建,把最小值挪到树根,相当于忽略最后一层
    for(int i=n/2;i>=1;i--)
    {
        down(i);
    }
    while (m -- )
    {
        printf("%d ",h[1]);
        h[1] = h[sz];
        sz--;
        down(1);
    }
    return 0;
}

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1e5+10;
int h[N],sz;
int n,m;
//下沉
void down(int u)
{
    int t = u;
    //根据小根堆性质,保证父亲节点是小于左右两个儿子的
    if(u*2 <= sz && h[u*2] < h[t]) t = u * 2;
    if(u*2+1 <= sz && h[u*2+1] < h[t]) t = u * 2 + 1;
    if(t != u)
    {
        swap(h[t],h[u]);//如果不同,需要交换两个节点
        down(t);//继续下沉
    }
}

int main()
{
    scanf("%d %d", &n, &m);
    for(int i=1;i<=n;i++) scanf("%d ",&h[i]);
    sz = n;//记得传一下长度给sz。
    //构建堆,从n/2序号开始创建,把最小值挪到树根,相当于忽略最后一层
    for(int i=n/2;i>=1;i--)
    {
        //这里非常巧妙,从后面开始去下沉,等到树根的时候,会第一个开始down,
        //会排好,不用担心乱序
        down(i);
    }
    while (m -- )
    {
        printf("%d ",h[1]);//取出最小值
        h[1] = h[sz]; //根据规则操作后续步骤
        sz--;
        down(1);
    }
    return 0;
}

3.2、模拟堆:此题同样来源于acwing

292b65b88d114511a3f94116ae743e8a.png4f98294c55924799b0a293a5893e1a2f.png


思路:

由于,此题需要记录一下第k个插入的数,所以需要用两个映射的数组去维护一下第k个插入的数,和当前堆中元素的下标,此处附上一位acwing评论区的一位大佬的讲解,我觉得讲的非常好,可以帮助此题理解两个数组的含义,建议先看代码,再看这个。代码中有详细注释,如果有错误欢迎指出~。

73ecd3a1ddcc41dea1f81ba5f441e919.png


AC代码: 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 1e5+10;
//h[]用来存堆,ph[]用来存第k个数对应的堆里的下标
//hp[]用来存堆下标下是第几个存入的数
int h[N],ph[N],hp[N],sz;
int n,m;

void heap_swap(int a,int b)
{
    swap(h[a],h[b]);//交换堆中的两个数值
    swap(hp[a],hp[b]);//在堆中对应的下标下是第几个存入的数,交换一下
    swap(ph[hp[a]],ph[hp[b]]);//交换一下堆中的下标
}
//下沉
void down(int u)
{
    int t = u;
    if(u*2 <= sz && h[u*2] < h[t]) t = u * 2;
    if(u*2+1 <= sz && h[u*2+1] < h[t]) t = u * 2 + 1;
    if(t != u)
    {
        heap_swap(t,u); //由于后面需要找到第k的数,所以不能用普通的交换,需要用我们手写的交换函数
        down(t);//下沉,直到不满足位置
    }
}
//上浮
void up(int u)
{
    //与根部比较,如果父亲大于儿子就需要上浮
    while(u/2 && h[u/2] > h[u]){
        heap_swap(u/2, u);
        u = u/2;
    }
}

int main()
{
    scanf("%d", &n);
    while (n -- )
    {
        char op[10];//根据题目要求输入字符串
        scanf("%s", op);
        if(!strcmp(op,"I"))
        {
            int x;
            scanf("%d", &x);
            m++;//第几个插入的
            sz++;//当前下标
            //ph表示第k插入的数在堆中的下标是多少
            //hp表示该数堆中下标对应第几个插入的数
            ph[m] = sz,hp[sz] = m;
            h[sz] = x;//堆中存入x
            up(sz);//上浮
        }
        else if(!strcmp(op,"PM")) //找到最小的数,就是树根
        {
            printf("%d\n",h[1]);
        }
        else if(!strcmp(op,"DM")) //注意交换后sz--;
        {
            heap_swap(1,sz);
            sz--;
            down(1);
        }
        else if(!strcmp(op,"D")) //删除第k个数
        {
            int k;
            scanf("%d", &k);
            k = ph[k]; //找到第k个数下的堆中的元素下标
            heap_swap(k,sz);
            sz--;
            down(k),up(k);//down和up只会执行一个操作,因为要么大,要么小
        }
        else
        {
            int k,x;
            scanf("%d %d", &k,&x);
            k = ph[k];
            h[k] = x;
            down(k),up(k);//同理
        }
    }
    return 0;
}

看会以上,大家可以去做一下这个题: 

P3378 【模板】堆 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题解在这里:

P3378 【模板】堆-CSDN博客


感谢观看~ 

  • 39
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值