算法板子:模拟堆——在堆中插入元素、查询堆中最小值、删除最小值、在任意位置插入元素、删除任意位置元素;构造小根堆、堆排序

1. 基础知识:

  1. 堆是一棵完全二叉树,除了最后一层外每一层都是满的,并且最后一层如果有右节点必有左节点
  2. 堆的节点从1开始编号; 如果一个节点的编号是i,那么该节点的左孩子是2i,右孩子是2i+1
  3. 小根堆中,父节点的值小于等于左右孩子,堆顶元素值最小; 大根堆中,父节点的值大于等于左右孩子,堆顶元素值最大;
  4. 这道题取出数组中前m小的元素,做法就是将数组先构造成小根堆,然后取m次堆顶元素

2. 堆的相关操作:

  1. 在堆中插入一个数就是把这个数插入到堆的末尾,然后将这个新节点up
  2. 求最小值直接取堆顶元素即可,堆顶元素的编号为1,值为h[1]
  3. 删除最小值就是删除堆顶元素,直接让堆中最后一个节点覆盖堆顶即可,覆盖后新顶点down
  4. 删除任意位置元素就是用最后一个节点覆盖该节点(也可以用swap函数覆盖,swap后该节点换到最后,然后size–从堆中剔除这个元素),覆盖好后该位置上的新节点up一下再down一下
  5. 修改任意位置元素就是修改该位置节点的值,改完值后再up一下down一下

在这里插入图片描述

3. 执行第四个和第五个操作的时候需要另外开两个数组ph和hp,如下:

  1. ph[i]代表第i个插入的节点在堆中的编号
  2. hp[i]代表编号为i的节点是第几个插入堆的

在这里插入图片描述

4. 实现:在堆中插入元素、查询堆中最小值、删除堆中最小值、在任意位置插入元素、删除任意位置元素:

#include <iostream>
using namespace std;

const int N = 1e5 + 10;

// h代表堆,h[i]代表第i个节点的值; h_size代表堆的大小; 要取最小值,是小根堆
// ph[i]代表第i个输入元素在堆中的下标
// hp[i]代表编号为i的节点是第几个插入的
int h[N], h_size, ph[N], hp[N];

// 交换堆中编号为i和编号为j的两个节点
void heap_swap(int i, int j)
{
    swap(ph[hp[i]], ph[hp[j]]);
    swap(hp[i], hp[j]);
    swap(h[i], h[j]);
}

// 将编号为i的节点上升到堆的合适位置
void up(int i)
{
    // i指向孩子节点,判断孩子节点的值是不是小于父节点,如果小于就交换两个节点
    while (i / 2 && h[i] < h[i / 2])
    {
        heap_swap(i, i / 2);
        i /= 2;
    }
}

// 将编号为i的节点下降到堆的合适位置
void down(int i)
{
    // i和初始t都指向父节点; t维护父节点和左右孩子中值最小的节点的编号
    int t = i;
    if (2 * i <= h_size && h[2 * i] < h[t]) t = 2 * i;
    if (2 * i + 1 <= h_size && h[2 * i + 1] < h[t]) t = 2 * i + 1;
    
    // 如果最小的节点不是父节点,交换父节点和孩子节点
    if (t != i)
    {
        heap_swap(i, t);
        down(t);
    }
}

int main()
{
    int n;
    cin >> n;
    
    // count记录这是第几个插入的元素
    int count = 0;
    
    while (n --)
    {
        string op;
        cin >> op;
        
        if (op == "I")
        {
            int x;
            cin >> x;
            
            // 在堆尾插入一个数,然后up到堆的合适位置
            h[ ++ h_size] = x;
            ph[ ++ count ] = h_size, hp[h_size] = count;
            up(h_size);
        }
        else if (op == "PM")
        {
            cout << h[1] << endl;
        }
        else if (op == "DM")
        {
            // 使用堆尾的节点覆盖堆顶,再将新堆顶down到堆的合适位置
            heap_swap(h_size, 1);
            h_size -- ;
            down(1);
        }
        else if (op == "D")
        {
            int k; 
            cin >> k;
            
            // 先得到第k个插入的元素在堆中的编号i
            int i = ph[k];
            // 再用堆尾节点覆盖该编号的节点,然后再up/down一下(up和down只会执行其中之一)
            heap_swap(h_size, i);
            h_size -- ;
            up(i), down(i);
        }
        else if (op == "C")
        {
            int k, x;
            cin >> k >> x;
            
            // 先得到第i个插入节点在堆中的编号
            int i = ph[k];
            // 修改该编号的节点的值
            h[i] = x;
            // 该完值后再up/down一下,让该节点调整到堆中合适的位置
            up(i), down(i);
        }
    }
    
    
    return 0;
}

5. 应用:堆排序——找出数组中前m个最小元素:

在这里插入图片描述

#include <iostream>
using namespace std;

const int N =1e5 + 10;

// h数组表示堆,从1开始编号,h[1]代表堆中编号为1的节点的值
// h_size表示堆的大小
int h[N], h_size;

// 当编号为i的节点的值大于左右孩子时,下沉该节点
void down(int i)
{
    // t指针始终指向父节点和左右孩子中值最小的节点
    // i是父节点; t一开始指向父节点
    int t = i;
    // 先判断有没有左右孩子,再将值最小的节点的编号用t保存
    if (2 * i <= h_size && h[2 * i] < h[t]) t = 2 * i;
    if (2 * i + 1 <= h_size && h[2 * i + 1] < h[t]) t = 2 * i + 1;
    if (t != i)
    {
        swap(h[i], h[t]);
        down(t);
    }
}

int main()
{
    int n, m;
    cin >> n >> m;
    
    // 初始堆
    for (int i = 1; i <= n; i ++ ) cin >> h[i];
    
    // 构造小根堆; 从n/2节点开始down; down完后堆顶是最小元素
    h_size = n;
    for (int i = n / 2; i >= 1; i -- ) down(i);
    
    
    while (m -- )
    {
        cout << h[1] << " ";
        
        // 用堆中最后一个节点覆盖堆顶,相当于删掉了堆顶
        h[1] = h[h_size];
        h_size -- ;
        // 再down新堆顶
        down(1);
    }
    
    return 0;
}
  • 10
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值