堆——堆排序、模拟堆

堆分为小根堆和大根堆,小根堆的父节点都要比子节点的值小,大根堆相反。
堆的存储使用一个一维数组来存储的,数组的下标我们是从1开始的,根节点下标为x的左孩子的下标为2x,右孩子的下标为2x+1.
在这里插入图片描述
如何手写一个堆?
1.插入一个数
2.求集合当中的最小值
3.删除最小值
4.删除任意一个元素
5.修改任意一个元素

例一:堆排序

在这里插入图片描述

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, m;
int h[N], cnt;//数组h表示堆,cnt表示堆里面存储的元素个数

void down(int u)
{
    int t = u;//t存储三个结点中存在的最小的结点的下标,初始化为当前结点u
    if(u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;// 左子节点存在并且小于当前结点,更新t的下标
    if(u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;//右子节点存在并且小于当前结点,更新t的下标
    if(u != t)//如果t==u意味着不用变动,u就是三个结点中拥有最小值的结点下标,否则交换数值
    {
        swap(h[u], h[t]);
        //交换数值后,t这个结点存储原本u的值,u存储存储t的值(三个数中的最小值)
        //u不用调整了,但t情况不明,可能需要调整。直到它比左右子节点都小
        down(t);
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) scanf("%d", &h[i]);
    cnt = n;
    
    for(int i = n / 2; i; --i) down(i);//把堆初始化成小根堆,从二叉树的倒数第二行开始,把数字大的下沉
    
    while(m--)
    {
        printf("%d ", h[1]);
        h[1] = h[cnt--];
        down(1);
    }
}

例题二:模拟堆

在这里插入图片描述

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;

//数组h表示堆,数组ph表示存放第k个插入点的下标
//数组hp表示存放堆中点的插入次序,cnt表示记录的是堆当前的个数多少
//ph数组主要用于帮助从idx映射到下标k
int h[N], ph[N], hp[N], cnt;

//hp[u]=k 则ph[k]=u 是映射关系
//之所以要进行这样的操作是因为 经过一系列操作 堆中的元素并不会保持原有的插入顺序
//从而我们需要对应到原先第K个堆中元素
//如果理解这个原理 那么就能明白其实三步交换的顺序是可以互换 
//h,hp,ph之间两两存在映射关系 所以交换顺序的不同对结果并不会产生影响
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;//m用来记录插入的数的个数
    //注意m的意义与cnt是不同的 cnt是记录堆中当前数据的多少
    //对应上文 m即是hp中应该存的值
    scanf("%d", &n);
    while(n--)
    {
        char op[5];
        int k, x;
        scanf("%s", op);
        if(op == "I")
        {
            scanf("%d", &x);
            cnt++;
            m++;
            ph[m] = cnt, hp[cnt] = m;
            up(cnt);
        }
        else if(op == "PM") printf("%d", h[1]);
        else if(op == "DM")
        {
            heap_swap(1, cnt);
            cnt--;
            down(1);
        }
        else if(op == "D")
        {
            scanf("%d", &k);
            int u = ph[k];//这里一定要用u=ph[k]保存第k个插入点的下标
            heap_swap(u, cnt);//因为在此处heap_swap操作后ph[k]的值已经发生 
            cnt--;
            up(u);
            down(u);
        }
        else
        {
            scanf("%d%d", &k, &x);
            k = ph[k];
            h[k] = x;
            up(k);
            down(k);
        }
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值