堆(用数组模拟堆)

一.什么是堆?

堆是一个完全二叉树,既是除了最后一层节点之外,上面的额所有结点都是满的,并且最后一层结点是从左到右依次排列的,类似下图。

小根堆:根节点小于等于两个子结点。

大顶堆:根节点大于等于两个子节点。

二.堆的存储方式

利用一维数组来存储。

三.堆的操作

  1. 插入一个数

  1. 求集合当中的最小值

  1. 删除最小值

  1. 删除任意一个元素

  1. 修改任意一个元素

以上五个操作都是基于两种方法实现的down()和up();其中down()函数是将该结点向下调整,up()函数是向上调整,这两种方法都能够让堆满足自身的性质,既是保持根节点小于等于两个子结点。

由于堆是根节点小于等于两个子结点(小顶堆为例),当我们需要插入一个数时,我们可以通过将数存储在最后一位,然后利用up()函数来向上调整,使之能够满足堆的性质;当我们需要求集合当中的最小值,堆顶的数值就是最小值,故直接返回堆顶的数值就行;删除最小值采用的方法是将堆顶的值赋值为最后一个数值,然后对堆顶的元素down();删除任意一个k元素时,将h[k]赋值为最后一个元素h[size],然后down();up();修改任意一个元素,直接将h[k] = x;然后down();up();

如下图所示,heap代表存储堆的数组,size为堆中数字的个数

//向下调整
void down(int u)
{
    int t = u;
    if(2 * u <= s && h[t] > h[2 * u]) t = 2 * u;
    if(2 * u + 1 <= s && h[t] > h[2 * u + 1]) t = 2 * u + 1;
    if(u != t)
    {
        swap(h[u], h[t]);
        down(t);
    }
    
}
//向上调整
void up(int u)
{
    while(u / 2 && h[u] < h[u / 2])    
    {
        heap_swap(h[u],h[u / 2]);
        u = u / 2;
    }
}

四、例题

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

  1. I x,插入一个数 x;

  1. PM,输出当前集合中的最小值;

  1. DM,删除当前集合中的最小值(数据保证此时的最小值唯一);

  1. D k,删除第 k 个插入的数;

  1. C k x,修改第 k 个插入的数,将其变为 x;

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

输入格式

第一行包含整数 N

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

输出格式

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

每个结果占一行。

数据范围

1≤N≤1051≤≤105

−109≤x≤109−109≤≤109

数据保证合法。

我的题目理解:了解了上面的五种不同的操作其实就差不多能够明白怎么做这道题了,但问题在于后两个操作是对第K个插入的数进行操作。因此要从新创建两个数组hp[k]和ph[k]。其中ph[k]=a则表示第k个插入的数的下标是a;而hp[a] = k则表示下标为a的数字是第k个插入的。既能够通过“它是第几个插入的数”找到“该数对应的下标”,也能够通过“该数对应的下标”找到“它是第几个插入的数”,这两者是相互映射的。

例如ph[k1] = a, hp[a] = k1; ph[k2] = b, hp[b] = k2;

当下标为a和b的两个数进行交换的时,不仅要交换h[a]和h[b]的值,同时也要交换ph[k1]和ph[k2]的值,而k1和k2可以通过hp[a]和hp[b]找到,所以交换时的代码为swap(ph[hp[a]],ph[hp[b]]);当ph[k1]和ph[k2]的值交换后,他们对应的映射同样也需要交换,这样才能保证两者对应映射,固有swap(hp[a], hp[b])。

#include <bits/stdc++.h>
#include <iostream>
#include <string.h>

using namespace std;
const int N = 100010;

int n;
int h[N],ph[N],hp[N],cnt;

//修改后的交换
void heap_swap(int a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a],hp[b]);
    swap(h[a], h[b]);
}

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

void up(int u)
{
    while(u / 2 && h[u] < h[u / 2])    
    {
        heap_swap(u, u / 2);
        u = u / 2;
    }
}

int main ()
{
    int m = 0;
    cin >> n;
    
    while(n --)
    {
        char op[5];
        int k, x;
        
        scanf("%s",op);
        
        if(!strcmp(op, "I"))
        {
            cin >> x;
            cnt ++;
            m ++;
            ph[m] = cnt, hp[cnt] = m;
            h[cnt] = x;
            up(cnt);
        }
        else if(!strcmp(op ,"PM")) cout << h[1] << endl;
        else if(!strcmp(op, "DM"))
        {
            heap_swap(1,cnt);
            cnt --;
            down(1);
        }
        else if(!strcmp(op,"D"))
        {
            cin >> k;
            k = ph[k];
            heap_swap(k,cnt);
            cnt --;
            up(k);
            down(k);
        }
        else if(!strcmp(op, "C"))
        {
            cin >> k >> x;
            k = ph[k];
            h[k] = x;
            up(k);
            down(k);
        }
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值