二叉堆——竞赛应用小结

目录

一、概念

二、图示说明

1.二叉堆

2.映射版二叉堆

三、操作解释

1.二叉堆

a.插入元素

b.输出最小值

c.删除最小值

d.删除第k个节点

2.映射版二叉堆

a.插入元素        

b.输出最小值

c.删除最小值

d.删除k对应的节点的值

e.修改k值对应节点的值

四、例题实践   

1.堆

a.题目描述

b.解题思路

c.代码实现

2.映射版堆   

a.题目描述

b.解题思路

c.代码实现


一、概念

二叉堆:二叉堆是一棵满足“堆性质”的完全二叉树,树上的每个节点都有一个权值。若树中的任意一个结点的权值都小于等于其父节点的权值,则称该二叉树为满足“大根堆性质”的大根堆。若树中的任意一个节点的权值都大于等于其父节点的权值,则称该二叉树为满足“小根堆性质”的小根堆。

注:完全二叉树,其叶子节点都在最后两层,且在最后一层集中于左侧的二叉树

二、图示说明

1.二叉堆

如图1,小根堆,依据层次序列存储方式(逐层从左到右依次为树中的节点编号,把此编号作为节点在数组中存储位置的下标),将每个结点存入一个数组中。

图1

2.映射版二叉堆

如图2,ph[]中每个k映射到堆中的一个结点,而hp[]反过来从堆的每个结点映射到对应的k

图2

三、操作解释

1.二叉堆

数组模拟,小根堆

a.插入元素

如图3,4,插入一个元素4到堆中。先将该元素放在数组最后面,然后通过和其父节点比对,然后不断向上移。

图3

         

图4

int h[N]; ///存储二叉堆
int si; ///存储二叉堆中最后一个结点的编号

void up(int x) {
    ///当结点x的父节点存在,且父节点大于节点x时满足条件
    if (x / 2 && h[x / 2] > h[x]) {
        swap(x, x / 2); ///将x节点和其父节点的权值进行交换
        up(x / 2); ///再交换后,让X/2节点继续向上比较
    }
}       
     
cin >> x;             
h[++si] = x; ///将x插入到数组h的最后一个位置               
up(si); ///将插入的x的结点位置传入到up,让x向上比较上移

b.输出最小值

cout << h[1] << endl; ///堆中的第一个结点即为最小值

c.删除最小值

如图5,6,7,8,9所示,删除最小值。先将最小值和堆数组的最后一个元素进行交换,然后删除堆数组的最后一个元素,然后再对交换后的顶部结点进行down操作,和子节点进行比较来向下沉。

图5
图6
图7

图8
图9
void down(int x) {
    int t = x;
    ///将节点x与其两个子节点进行比较,记录x最小值
    if (2 * x <= si && h[t] > h[2 * x])  t = 2 * x;
    if (2 * x + 1 <= si && h[t] > h[2 * x + 1])  t = 2 * x + 1;
    if (t != x) { ///当最小值节点不为x时,将节点x与记录的t节点进行交换
        heap_swap(t, x);
        down(t); ///让当前节点t继续向下比较
    }
}    
             
swap(1, si); ///将节点1和最后一个节点进行权值交换            
si--; ///将堆中的最后一个节点减去           
down(1); ///将节点1向下比较

d.删除第k个节点

如图10,11,12,13,删除第2个节点。先将堆数组中第2个节点与最后一个节点进行交换,然后删除最后一个节点,最后让交换后的第2个节点进行向下比较。

图10
图11

         

图12

         

图13
void down(int x) {
    int t = x;
    ///将节点x与其两个子节点进行比较,记录x最小值
    if (2 * x <= si && h[t] > h[2 * x])  t = 2 * x;
    if (2 * x + 1 <= si && h[t] > h[2 * x + 1])  t = 2 * x + 1;
    if (t != x) { ///当最小值节点不为x时,将节点x与记录的t节点进行交换
        heap_swap(t, x);
        down(t); ///让当前节点t继续向下比较
    }
}

void up(int x) {
    ///当结点x的父节点存在,且父节点大于节点x时满足条件
    if (x / 2 && h[x / 2] > h[x]) {
        heap_swap(x, x / 2); ///将x节点和其父节点的权值进行交换
        up(x / 2); ///再交换后,让X/2节点继续向上比较
    }
} 

cin >>k;          
swap(k, si); ///将节点k和最后一个节点进行权值交换           
si--; ///将堆中的最后一个节点减去            
down(k),up(k); ///将节点k,进行向下比较或向上比较(两个中只会进行一个)

2.映射版二叉堆

a.插入元素        

如图14到图17所示,插入元素4。先将元素4加入堆数组的最后一个位置,然后建立k值到结点,结点到k值的映射关系,然后up向上比较元素4的父节点点。比较后发现,元素4比父节点小,故进行交换,先将k值到节点的关系进行交换,然后再将节点到k值的关系进行交换,最后再将两个节点的数值进行交换,然后向上比较发现无法替换,插入结束。

图14
图15
图16
图17

        

int ph[N]; ///存储k值到节点的对应关系
int hp[N]; ///存储节点到k值的对应关系
int si; ///存储二叉堆中最后一个结点的编号
int idx; ///存储ph[]数组中k值的最后一个位置

void heap_swap(int a, int b) {
    swap(ph[hp[a]], ph[hp[b]]); ///让a,b节点中,k值指向节点的对应关系调换
    swap(hp[a], hp[b]); ///再让节点指向k值的对应关系调换
    swap(h[a], h[b]); ///最后再将两个节点的权值进行调换
} 

void up(int x) {
    ///当结点x的父节点存在,且父节点大于节点x时满足条件
    if (x / 2 && h[x / 2] > h[x]) {
        heap_swap(x, x / 2); ///将x节点和其父节点的权值进行交换,并修改对应关系
        up(x / 2); ///再交换后,让X/2节点继续向上比较
    }
}            
          
h[++si] = x; ///将x插入到堆数组的最后一个位置
                     
///将最后一个结点指向对应的k值,k值指向最后一个节点           
hp[si] = ++idx, ph[idx] = si; 
up(si); ///将插入的x的结点位置传入到up,让x向上比较上移

b.输出最小值

cout << h[1] << endl; ///堆中的第一个结点即为最小值

c.删除最小值

如图18到图22所示,删除最小值。先将顶部结点与最后一个结点及其映射关系交换,然后再将堆数组的最后一个节点去掉,然后再让顶部结点进行向下比较。

         

图18

  

图19
图20
图21

图22

                         

void down(int x) {
    int t = x;
    ///将节点x与其两个子节点进行比较,记录x最小值
    if (2 * x <= si && h[t] > h[2 * x])  t = 2 * x;
    if (2 * x + 1 <= si && h[t] > h[2 * x + 1])  t = 2 * x + 1;
    if (t != x) { ///当最小值节点不为x时,将节点x与记录的t节点进行交换,并修改其对应关系
        heap_swap(t, x);
        down(t); ///让当前节点t继续向下比较
    }
}         
                         
heap_swap(1, si); ///将节点1和最后一个节点进行权值交换,并调整对应关系            
si--; ///将堆中的最后一个节点减去            
down(1); ///将节点1向下比较

d.删除k对应的节点的值

如图23,24,25,26所示,删除值k9对应的节点。先让k9对应节点与最后一个节点交换权值,及其对应关系,然后删除最后一个节点,最后让交换前k9对应节点进行向下或向上比较。

图23
图24
图25

  

图26

        

cin >> k;           
int t = ph[k]; ///记录输入k值对应的结点           
heap_swap(t, si); ///交换k值对应节点和最后一个结点及其对应关系            
si--; ///减去堆中最后一个节点          
down(t),up(t);///让输入k值时,对应的结点进行向上或向下比较

e.修改k值对应节点的值

如图27,28,29所示,将k4对应的节点权值修改为4。先找到k4对应节点权值,然后将其修改为4,最后再让该节点进行向上或向下比较。

图27

图28
图29
cin >> k >> x;          
h[ph[k]] = x; ///让k值所对应的节点权值修改为x           
down(ph[k]),up(ph[k]); ///然后让k值所对应的节点,进行向上或向下比较

四、例题实践   

1.堆

a.题目描述

题目来源:acwing

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

输入格式

第一行包含整数 n 和 m。

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

输出格式

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

数据范围

1≤m≤n≤1e5,
1≤数列中元素≤1e9

输入样例:

5 3
4 5 1 3 2

输出样例:

1 2 3

b.解题思路

思路:先将输入的数存入数组中,然后再从最后一个结点的父节点到第一个结点,每个结点进行向下比较。然后m次输出一个最小值,每一次输出完后,删除最小值。

c.代码实现

#include<iostream>

using namespace std;

const int N=1e5+10;

int h[N],si;

void down(int x){
    
    int t=x;
    if(2*x<=si&&h[t]>h[2*x])  t=2*x;
    if(2*x+1<=si&&h[t]>h[2*x+1])  t=2*x+1;
    if(x!=t){
        swap(h[x],h[t]);
        down(t);
    }
}

int main(){
    
    int n, m;
    cin >>n >>m;
    for(int i=1;i<=n;i++)   cin >>h[i];
    si=n;
    for(int i=n/2;i>=1;i--) down(i); ///由最后一个节点的父节点开始,向下比较
    
    while(m--){
        cout <<h[1] <<' ';
        ///删除最小值
        h[1]=h[si];
        si--;
        down(1);
    }
    
    return 0;
}

2.映射版堆   

a.题目描述

维护一个集合,初始时集合为空,支持如下几种操作:

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

现在要进行 N 次操作,对于所有第 2 个操作,输出当前集合的最小值。

输入格式

第一行包含整数 N。

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

输出格式

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

每个结果占一行。

数据范围

1≤N≤1e5
−1e9≤x≤1e9
数据保证合法。

输入样例:

8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM

输出样例:

-10
6

b.解题思路

思路:因为在题目中,要求输出能最小值,同时还要能修改k值对应节点的权值,所以采用映射版堆来维护该集合

c.代码实现

#include<iostream>

using namespace std;

const int N = 1e5 + 10;

int h[N]; ///存储二叉堆
int ph[N]; ///存储k值到节点的对应关系
int hp[N]; ///存储节点到k值的对应关系
int si; ///存储二叉堆中最后一个结点的编号
int idx; ///存储ph[]数组中k值的最后一个位置
char op[2];
int n;

void heap_swap(int a, int b) {
    swap(ph[hp[a]], ph[hp[b]]); ///让a,b节点中,k值指向节点的对应关系调换
    swap(hp[a], hp[b]); ///再让节点指向k值的对应关系调换
    swap(h[a], h[b]); ///最后再将两个节点的权值进行调换
}

void down(int x) {
    int t = x;
    ///将节点x与其两个子节点进行比较,记录x最小值
    if (2 * x <= si && h[t] > h[2 * x])  t = 2 * x;
    if (2 * x + 1 <= si && h[t] > h[2 * x + 1])  t = 2 * x + 1;
    if (t != x) { ///当最小值节点不为x时,将节点x与记录的t节点进行交换,并修改其对应关系
        heap_swap(t, x);
        down(t); ///让当前节点t继续向下比较
    }
}   

void up(int x) {
    ///当结点x的父节点存在,且父节点大于节点x时满足条件
    if (x / 2 && h[x / 2] > h[x]) {
        heap_swap(x, x / 2); ///将x节点和其父节点的权值进行交换,并修改对应关系
        up(x / 2); ///再交换后,让X/2节点继续向上比较
    }
}

int main() {
    cin >> n;
    while (n--) {
        cin >> op;
        int k;
        long long x;
        if (op[0] == 'I') {
            cin >> x; 
            h[++si] = x; ///将x插入到堆数组的最后一个位置
            
            ///将最后一个结点指向对应的k值,k值指向最后一个节点
            hp[si] = ++idx, ph[idx] = si; 
            up(si); ///将插入的x的结点位置传入到up,让x向上比较上移
        }
        else if (op[0] == 'P') {
            cout << h[1] << endl; ///堆中的第一个结点即为最小值
        }
        else if (op[0] == 'D' && op[1] == 'M') {
            heap_swap(1, si); ///将节点1和最后一个节点进行权值交换,并调整对应关系
            si--; ///将堆中的最后一个节点减去
            down(1); ///将节点1向下比较
        }
        else if (op[0] == 'D' && op[1] != 'M') {
            cin >> k; 
            int t = ph[k]; ///记录输入k值对应的结点
            heap_swap(t, si); ///交换k值对应节点和最后一个结点及其对应关系
            si--; ///减去堆中最后一个节点
            down(t),up(t);///让输入k值时,对应的结点进行向上或向下比较
        }
        else {
            cin >> k >> x;
            h[ph[k]] = x; ///让k值所对应的节点权值修改为x
            down(ph[k]),up(ph[k]); ///然后让k值所对应的节点,进行向上或向下比较
        }
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值