【algorithm】算法学习----堆

堆涉及到的五个问题:

  • 插入一个数

  • 求集合中的最小值

  • 删除最小值

  • 插入任意一个元素

  • 删除任意一个元素

对于堆,这里需要使用树来存储。

这里以小根堆来举例:

什么是小根堆,小根堆就是父节点比其两个孩子节点都要小。

由此我们可以想到根节点就是当前堆的最小值。

堆其实是一个完全二叉树。

什么是完全二叉树?

就是除了最后一层,其余层都是满度(都是2),并且最后一层的是从左到右排列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cuRez63D-1662215980468)(D:\MarkdownImages\2022-09-01-21-44-12-image.png)]
但是如果使用节点point+value比较麻烦并且不适用于算法题里面。

所以我们使用一维数组来存储:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bC9VgIQH-1662215980469)(D:\MarkdownImages\2022-09-03-10-57-48-image.png)]
因此我们对于这个完全二叉树每次插入一个节点就有两个操作down()up()操作。

要么网上调,要么往下挑。

down的逻辑:<还是以小根堆为例>

如果当前的节点比其两个孩子的节点都要大,因此将该节点与其孩子节点中最小的进行交换。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q22ypat9-1662215980469)(D:\MarkdownImages\2022-09-03-19-20-26-image.png)]
up的逻辑与:

如果当前的节点比其父亲节点要大,则交换这两个节点,一直到上升到合适的位置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I29Q3lQ5-1662215980470)(D:\MarkdownImages\2022-09-03-19-20-42-image.png)]
因此对于开头的五个问题可以有以下解法:

假设堆为heap[N],heap数组的大小为size.

插入一个数heap[++size]=x;up(x)
求集合中的最小值heap[1]
删除最小值使用最后一个元素覆盖根节点,然后删除最后一个元素。然后再让最后一个元素Down() heap[1]=heap[size];size–;down(1)
插入任意一个元素heap[k]=x;Down(k)/Up(k);
删除任意一个元素heap[k]=heap[size];size–;Down(k)/Up(k);

概念理解题:838. 堆排序 - AcWing题库

正式做题可能会遇到一个问题,我输入的是一个数组,我怎么把它构建成堆呢?

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e5+10;

int h[N];
int s,m,n;

void down(int u)
{
    int t=u;
    if(2*u<=s&&h[2*u]<h[t]) t=2*u;
    if(2*u+1<=s&&h[2*u+1]<h[t]) t=2*u+1;
    if(t!=u)
    {
        swap(h[t],h[u]);
        down(t);
    }
}
int main()
{
    cin>>n>>m;

    for(int i=1;i<=n;i++) cin>>h[i];

    s=n;
//这里就涉及到了如何构建堆:
    for(int i=n/2;i>=1;i--) down(i);

    while(m--)
    {
        cout<<h[1]<<" ";
        h[1]=h[s];
        s--;
        down(1);
    }
    return 0;
}
//Down()函数
void down(int u)
{
    int t=u;
    if(2*u<=s&&h[2*u]<h[t]) t=2*u;
    if(2*u+1<=s&&h[2*u+1]<h[t]) t=2*u+1;
    if(t!=u)
    {
        swap(h[t],h[u]);
        down(t);
    }
}
//Up()函数
void up(int u)
{
    while(u/2&&h[u/2]>h[u])
    swap(h[u/2],h[u]);
    u/=2;
}

练习题:839. 模拟堆 - AcWing题库

其实我们仔细想啊,堆节点的下标我们是知道的,我们只需要一个数组存储下标为i的数是第j次插入的。但是看一下这道题目的要求:

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

知道第k个插入的数我们还得找到对应数的下标,因此我们有了ph与hp这两个互指指针。

ph[k]表示第k个插入的数在数组的下标什么。

hp[k]表示堆里下标为k的点是第几个被插入的
在这里插入图片描述


#include <iostream>
#include <algorithm>
#include <string.h>

using namespace std;

const int N = 100010;

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 (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
    if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if (u != t)
    {
        heap_swap(u, t);
        down(t);
    }
}

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

int main()
{
    int n, m = 0;
    scanf("%d", &n);
    while (n -- )
    {
        char op[5];
        int k, x;
        scanf("%s", op);
        if (!strcmp(op, "I"))
        {
            scanf("%d", &x);
            cnt ++ ;
            m ++ ;
            ph[m] = cnt, hp[cnt] = m;
            h[cnt] = x;
            up(cnt);
        }
        else if (!strcmp(op, "PM")) printf("%d\n", h[1]);
        else if (!strcmp(op, "DM"))
        {
            heap_swap(1, cnt);
            cnt -- ;
            down(1);
        }
        else if (!strcmp(op, "D"))
        {
            scanf("%d", &k);
            k = ph[k];
            heap_swap(k, cnt);
            cnt -- ;
            up(k);
            down(k);
        }
        else
        {
            scanf("%d%d", &k, &x);
            k = ph[k];
            h[k] = x;
            up(k);
            down(k);
        }
    }

    return 0;
}

补充

strcmp()函数

该函数返回值如下:

  • 如果返回值小于 0,则表示 str1 小于 str2。
  • 如果返回值大于 0,则表示 str1 大于 str2。s
  • 如果返回值等于 0,则表示 str1 等于 str2。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明璐花生牛奶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值