堆(基本介绍,代码实现,以及例题)

1.堆的功能

1.插入一个数
2.求集合当中最小值
3.删除最小值
(1-3stl能做)
4.删除任意一个元素
5.修改任意一个元素

堆的两个重要性质:
堆是一棵完全二叉树
小根堆:每一个结点的值都小于等于 左右儿子

2.堆的存储

用数组存储,1号点是根节点,x的左儿子是2x,右儿子是2x+1
下标从1开始比较方便
ph中p是指针,h是堆

3.堆的函数

down(x),往下调整
up(x),往上调整
heap_swap,交换元素和映射

down和up操作

假设有一个堆是这样的
在这里插入图片描述
很显然结点3不满足堆的性质,这时我们对其进行down操作
先比较3、1、4谁更小,明显1更小,越小越优秀,于是我们把1拿上来,3放下去。
在这里插入图片描述

这时我们继续对结点3进行down操作,比较3、2、5的大小,2是最小的,于是交换2和3
在这里插入图片描述
这样down操作就完成了。
up操作同理,只不过是up操作的结点只需要与父亲结点比较就可以了。

5.堆的初始化

假设有三个元素:213,初始化时,三个结点都是独立的堆
在这里插入图片描述
首先,我们区n/2(n在这里是3)的元素,它的孩子结点是1和3,且1和3都是堆
在这里插入图片描述
然后我们对2进行down操作
在这里插入图片描述
这样,堆的初始化就完成了。

6.删除堆中最小的元素

先把堆中最后一个结点和根结点互换,然后删除最后一个结点,再对新的根结点进行down操作。

ACWING838 堆排序
输入一个长度为 n 的整数数列,从小到大输出前 m 小的数。

输入格式
第一行包含整数 n 和 m。

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

输出格式
共一行,包含 m 个整数,表示整数数列中前 m 小的数。

数据范围
1≤m≤n≤105,
1≤数列中元素≤109
输入样例:
5 3
4 5 1 3 2
输出样例:
1 2 3

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int h[N], sz, 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(u != t)
    {
        swap(h[u], h[t]);
        down(t);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> m;
    sz = n;
    for(int i = 1; i <= n; i++)
        cin >> h[i];
    for(int i = n / 2; i; i--)
        down(i);
    while(m--)
    {
        cout << h[1] << ' ';
        h[1] = h[sz--];
        down(1);
    }
    return 0;
}

这道题主要注意初始化堆时要从n/2开始。因为要从满足以下性质的结点开始down:
1.左右儿子满足堆的性质。
2.下标最大(因为要往上遍历)
3.不是叶结点(叶节点一定满足堆的性质)

堆排序的时间复杂度是O(n*logn)

ACWING839 模拟堆
维护一个集合,初始时集合为空,支持如下几种操作:
“I x”,插入一个数x;
“PM”,输出当前集合中的最小值;
“DM”,删除当前集合中的最小值(数据保证此时的最小值唯一);
“D k”,删除第k个插入的数;
“C k x”,修改第k个插入的数,将其变为x;
现在要进行N次操作,对于所有第2个操作,输出当前集合的最小值。

输入格式
第一行包含整数N。

接下来N行,每行包含一个操作指令,操作指令为”I x”,”PM”,”DM”,”D k”或”C k x”中的一种。

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

每个结果占一行。

数据范围
1≤N≤105
−109≤x≤109
数据保证合法。

输入样例:
8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM
输出样例:
-10
6

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int h[N], hp[N], ph[N], isz, sz, n;
void heap_swap(int u, int v)
{
    swap(h[u], h[v]);
    swap(hp[u], hp[v]);
    swap(ph[hp[v]], ph[hp[u]]);
}
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(u != t)
    {
        heap_swap(u, t);
        down(t);
    }
}
void up(int u)
{
    if(u / 2 && h[u / 2] > h[u])
    {
        heap_swap(u / 2, u);
        up(u / 2);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int k, x;
    string op;
    cin >> n;
    while(n--)
    {
        cin >> op;
        if(op == "I")
        {
            cin >> x;
            sz++;
            isz++;
            h[sz] = x;
            hp[sz] = isz;
            ph[isz] = sz;
            down(sz);
            up(sz);
        }
        if(op == "PM")
            cout << h[1] << '\n';
        if(op == "DM")
        {
            heap_swap(1, sz);
            sz--;
            down(1);
        }
        if(op == "D")
        {
            cin >> k;
            int u = ph[k];
            heap_swap(u, sz);
            sz--;
            down(u);
            up(u);
        }
        if(op == "C")
        {
            cin >> k >> x;
            h[ph[k]] = x;
            down(ph[k]);
            up(ph[k]);
        }
    }
    return 0;
}

这里要注意两个新增加的数据结构:hp和ph数组。ph中p是指针,h是堆,hp同理。
ph[i]:存储第i个插入的数在堆中的下标
hp[i]:存储在堆中下标为i的数是第几个插入的

可以看出,hp和ph类似于反函数。
理解了这个,我们就能理解heap_swap的含义:交换堆下标为v和u的两个元素的所有映射——h,hp,ph。由于这三个映射互不干扰,所以交换的顺序任意。

然后就是注意down和up的参数,如果是参数是ph,并且之前要用到heap_swap操作的话,那么一定要用临时变量把ph存起来,否则ph会变,就背离了编程者的初衷。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值