学习笔记:堆

性质

从二叉堆的结构说起,它是一棵二叉树,并且是完全二叉树,每个结点中存有一个元素(或者说,有个权值)。

堆性质:父亲的权值不小于儿子的权值(大根堆)。同样的,我们可以定义小根堆。本文以大根堆为例。

由堆性质,树根存的是最大值(getmax 操作就解决了)。

操作

插入

插入操作是指向二叉堆中插入一个元素,要保证插入后也是一棵完全二叉树。

最简单的方法就是,最下一层最右边的叶子之后插入。

如果最下一层已满,就新增一层。

插入之后可能会不满足堆性质?

向上调整:如果这个结点的权值大于它父亲的权值,就交换,重复此过程直到不满足或者到根。

可以证明,插入之后向上调整后,没有其他结点会不满足堆性质。

向上调整的时间复杂度是 $O(\log n)$ 的。

void insert(int x){
    cnt++;heap[cnt] = x;
    int now = cnt, nxt;
    while(now >= 0){
        nxt = now >> 1;
        if(heap[nxt] > heap[now])swap(heap[nxt], heap[now]);
        else break;
        now = nxt;
    }
}

这里再附上一张图方便理解:

删除

删除操作指删除堆中最大的元素,即删除根结点。

但是如果直接删除,则变成了两个堆,难以处理。

所以不妨考虑插入操作的逆过程,设法将根结点移到最后一个结点,然后直接删掉。

然而实际上不好做,我们通常采用的方法是,把根结点和最后一个结点直接交换。

于是直接删掉(在最后一个结点处的)根结点,但是新的根结点可能不满足堆性质……

向下调整:在该结点的儿子中,找一个最大的,与该结点交换,重复此过程直到底层。

可以证明,删除并向下调整后,没有其他结点不满足堆性质。

时间复杂度 $O(\log n)$。

void del(){
    swap(heap[1], heap[cnt]);cnt--;
    int now = 1, nxt;
    while((now << 1) <= cnt){
        nxt = now << 1;
        if(nxt + 1 <= cnt && heap[nxt + 1] < heap[nxt])nxt++;
        if(heap[nxt] < heap[now])swap(heap[nxt], heap[now]);
        else break;
        now = nxt;
    }
}

查询

直接输出堆顶元素即可。

int query(){return heap[1];}

实现

可以考虑用一个序列来维护一个堆,其中第 $i$ 个点的左右儿子分别为第 $2i$ 和 $2i+1$ 个点。

代码

#include <iostream>
#define MAXN 1000005
using namespace std;
int n, op, x, heap[MAXN], cnt;
int read(){
    int t = 1, x = 0;char ch = getchar();
    while(!isdigit(ch)){if(ch == '-')t = -1;ch = getchar();}
    while(isdigit(ch)){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * t;
}
void write(int x){
    if(x < 0){putchar('-');x = -x;}
    if(x >= 10)write(x / 10);
    putchar(x % 10 ^ 48);
}
void insert(int x){
    cnt++;heap[cnt] = x;
    int now = cnt, nxt;
    while(now >= 0){
        nxt = now >> 1;
        if(heap[nxt] > heap[now])swap(heap[nxt], heap[now]);
        else break;
        now = nxt;
    }
}
int query(){return heap[1];}
void del(){
    swap(heap[1], heap[cnt]);cnt--;
    int now = 1, nxt;
    while((now << 1) <= cnt){
        nxt = now << 1;
        if(nxt + 1 <= cnt && heap[nxt + 1] < heap[nxt])nxt++;
        if(heap[nxt] < heap[now])swap(heap[nxt], heap[now]);
        else break;
        now = nxt;
    }
}
int main(){
    n = read();
    for(int i = 1 ; i <= n ; i ++){
        op = read();
        switch(op){
            case 1:x = read();insert(x);break;
            case 2:write(query());putchar('\n');break;
            case 3:del();break;
            default:cout << "jtskdjfkxqsvivo50wdnc" << endl;break;
        }
    }
    return 0;
}

不过这边更加推荐直接使用 STL 中的优先队列(主要是为了偷懒

#include <iostream>
#define MAXN 1000005
using namespace std;
int n, op, x;
priority_queue <int> q;
int read(){
    int t = 1, x = 0;char ch = getchar();
    while(!isdigit(ch)){if(ch == '-')t = -1;ch = getchar();}
    while(isdigit(ch)){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * t;
}
void write(int x){
    if(x < 0){putchar('-');x = -x;}
    if(x >= 10)write(x / 10);
    putchar(x % 10 ^ 48);
}
int main(){
    n = read();
    for(int i = 1 ; i <= n ; i ++){
        op = read();
        switch(op){
            case 1:x = read();q.push(~x);break;
            case 2:write(~q.top());putchar('\n');break;
            case 3:q.pop();break;
            default:cout << "jtskdjfkxqsvivo50wdnc" << endl;break;
        }
    }
    return 0;
}
posted @ 2023-09-18 15:26  tsqtsqtsq  阅读(6)  评论(0编辑  收藏  举报  来源