• 堆——堆排序、模拟堆


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

    例一:堆排序

    在这里插入图片描述

    #include 
    #include 
    
    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    例题二:模拟堆

    在这里插入图片描述

    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
  • 相关阅读:
    Codeforces Round 940 (Div. 2) C. How Does the Rook Move?
    001、Nvidia Jetson Nano Developer KIT(b01)-系统与登录
    TCP/IP详解卷一第三章“链路层”概要总结(未完编辑中)
    华为交换机S5735S-L24T4S-QA2无法telnet远程访问
    pycharm 快捷键 及 高级设置
    Windows10系统安装telnet命令
    新品速看丨创新微MinewSemi正式推出GNSS高精度卫星定位导航模块
    跨域问题及前端的解决方法
    vuInhub靶场实战系列--Kioptrix Level #4
    剪画小程序:如何快速将视频中的文字提取出来?这3个方法都很不错!
  • 原文地址:https://blog.csdn.net/qq_59702185/article/details/126974711