zoukankan      html  css  js  c++  java
  • 数据结构--堆

    堆是一棵完全二叉树,可以O(1)取最值,O(logn)删除最值,或插入元素。
    堆的性质:每个非叶子节点比它的孩子都大(大根堆/大顶堆)/每个非叶子节点比它的孩子都小(小根堆/小顶堆)。
    从上面的性质可以推出堆的根就是最值。
    堆的常用操作:top返回堆顶,pop弹出堆顶,push压入新元素。
    后两个操作时间是O(logn),因为弹出或压入后都要调整堆的结构,而调整的次数不超过堆的深度。
    然而堆是完全二叉树,深度不超过O(logn),因此pop和push都是O(logn)的。
    接下来是手写堆的代码:

    //完全二叉树的保存方式:
    //根:1
    //节点i的左儿子i*2,右儿子i*2+1
    #include<bits/stdc++.h>
    using namespace std;
    int n,tot;
    int t[1000001];
    void push(int x){
        tot++;
        t[tot]=x;
        int u=tot;
        while(t[u>>1]>t[u]){
            swap(t[u>>1],t[u]);
            u>>=1;
        }
    }
    void pop(){
        if(tot==0){
            return;
        }
        swap(t[1],t[tot]);
        tot--;
        int u=1;
        while(((u<<1<=tot)&&(t[u]>t[u<<1]))||(((u<<1)+1<=tot)&&(t[u]>t[(u<<1)+1]))){
            if((u<<1)+1>tot){
                swap(t[u],t[u<<1]);
                break;
            }
            if(t[u<<1]<t[(u<<1)+1]){
                swap(t[u],t[u<<1]);
                u<<=1;
            }
            else{
                swap(t[u],t[(u<<1)+1]);
                u<<=1;
                u++;
            }
        }
    }
    int top(){
        return t[1];
    }
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            int num;
            scanf("%d",&num);
            if(num==1){
                int x;
                scanf("%d",&x);
                push(x);
            }
            else if(num==2){
                printf("%d
    ",top());
            }
            else{
                pop();
            }
        }
        return 0;
    }

    然后其实在竞赛中更常用的是系统自带堆——优先队列priority_queue,很方便,就是慢了一倍左右。

    关于优先队列的使用方法:
    priority_queue< T > (T为类型)(后面括号内为时间复杂度)
    1. T top(void):返回堆顶(即最值);(1)
    2. void pop(void):弹出堆顶;(logn)
    3. void push(T):压入新元素;(logn)
    4. bool empty(void):如果堆为空返回true,否则返回false;(1)
    5. int size(void):返回堆的元素数量;(1)
    另一种定义方式:(建议)
    priority_queue< T , vector< T > ,less< T > > (大根堆)
    priority_queue< T , vector< T > ,greater< T > > (小根堆)
    用自定义类型的前提是那个类型有定义小于号(大根堆)/ 大于号(小根堆)

    我这里给出优先队列的使用代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,tot;
    priority_queue<int,vector<int>,greater<int> > q;
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            int num;
            scanf("%d",&num);
            if(num==1){
                int x;
                scanf("%d",&x);
                q.push(x);
            }
            else if(num==2){
                printf("%d
    ",q.top());
            }
            else{
                if(!q.empty())
                q.pop();
            }
        }
        return 0;
    }

    具体时间差就是:
    手写堆828ms
    优先队列1677ms


    到这里,堆的初步介绍就结束了。
    接下来最重要的还是题。

    洛谷 P3378 【模板】堆

    写完堆先交这个题,确保时间不超1000ms就基本合格。

    洛谷 P1090 合并果子

    这个题其实本身很简单,每次取两堆最小的合并再放入,重复到只剩一堆。
    我想把这个题放在这儿的目的就是要说这种思想很重要,就是放入再取出的思想。
    有时候这种思想可以使你的操作变得可以撤销,可以过好多题。
    有一次我和@hzy一起去培训的时候就碰到了这么一个题,他想出来了,可是没时间写了,错失了100分,最后就让我这么一个只会暴力的拿了第一(笑)。

    洛谷 P2085 最小函数值

    这是堆的一个经典应用:求若干个单调序列中的第m小(大)
    这题我们可以将每个函数看成一个单调上升的序列,这样最小值一定在某个队首,取出最小值后次小值就一定还在某个队首,这样,我们就可以一开始将所有队首放入堆中,然后每次取出某个值后,再将它的序列中的下一个值放入堆中;如此重复m次就可以得到第m小。
    贴代码:

    #include<bits/stdc++.h>
    using namespace std;
    struct func{
        int a,b,c,x,v;
        void calc(){
            v=a*x*x+b*x+c;
        }
        bool operator > (const func& y) const {
            return v>y.v;
        }
    }f[10001];
    int n,m;
    priority_queue<func,vector<func>,greater<func> > q;
    
    int main(){
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%d %d %d",&f[i].a,&f[i].b,&f[i].c);
            f[i].x=1;
            f[i].calc();
            q.push(f[i]);
        }
        for(int i=1;i<=m;i++){
            func g=q.top();
            q.pop();
            printf("%d ",g.v);
            g.x++;
            g.calc();
            q.push(g);
        }
        return 0;
    }

    洛谷 P1631 序列合并

    这题和上面那个本质上一样,排序后,将A序列中一个值和B序列中所有值的配对看做一个单调上升序列,像上面那样做就好了。
    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int A[100001],B[100001];
    struct func{
        int a,b;
        bool operator > (const func& y) const {
            return A[a]+B[b]>A[y.a]+B[y.b];
        }
    }f[10001];
    int n;
    priority_queue<func,vector<func>,greater<func> > q;
    
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d",&A[i]);
        }
        for(int i=1;i<=n;i++){
            scanf("%d",&B[i]);
        }
        sort(A+1,A+n+1);
        sort(B+1,B+n+1);
        for(int i=1;i<=n;i++){
            func g;
            g.a=i;
            g.b=1;
            q.push(g);
        }
        for(int i=1;i<=n;i++){
            func g=q.top();
            q.pop();
            printf("%d ",A[g.a]+B[g.b]);
            g.b++;
            if(g.b<=n)
            q.push(g);
        }
        return 0;
    }

    洛谷 P1801 黑匣子_NOI导刊2010提高(06)

    这题虽然算是一个平衡树模板,可是也是一道堆的进阶题目,难度达到了提高+/省选-;
    这个题中我们要开两个堆,一个大顶堆q1,一个小顶堆q2,大顶堆的顶部对着小顶堆的顶部(q1.top()<q2.top()),每次压入一个新元素的时候都要判断往哪边压,让其始终保持这个性质。
    此时这两个堆合在一起被称为对顶堆。
    我们还要记录一个变量size来记录q1的大小;每次GET的时候,如果size < i,就一直将q2的顶部压入q1;
    如果size > i,就一直将q1的顶部压入q2;直到size==i之后q1的顶部就是查询结果。
    代码不难写出:

    #include<bits/stdc++.h>
    using namespace std;
    int a[200001],u[200001];
    int n,m;
    priority_queue<int,vector<int>,less<int> > q1;
    priority_queue<int,vector<int>,greater<int> > q2;
    int main(){
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        for(int i=1;i<=m;i++){
            scanf("%d",&u[i]);
        }
        sort(u+1,u+m+1);
        int k=1;
        int sz=0;
        for(int i=1;k<=m;i++){
            if(q2.empty()||a[i]<q2.top()){
                q1.push(a[i]);
                sz++;
            }
            else{
                q2.push(a[i]);
            }
            while(i==u[k]){
                while(sz<k){
                    q1.push(q2.top());
                    q2.pop();
                    sz++;
                }
                while(sz>k){
                    q2.push(q1.top());
                    q1.pop();
                    sz--;
                }
                printf("%d
    ",q1.top());
                k++;
            }
        }
        return 0;
    }

    题目我就放这么多,关键还是要能想到用堆,数据结构对比赛只是辅助。
    end

  • 相关阅读:
    Swift app中的Crash捕获与处理
    10 Things ASP.NET Developers Should Know About Web.config Inheritance and Overrides(转)
    Browserify使用指南(转)
    开启mac terminal 命令/路径自动补全功能
    selenium--定位元素
    selenium--断言方法
    selenium--unittest框架
    苹果手机对应版本
    Pycharm2019激活
    接口上传图片方法
  • 原文地址:https://www.cnblogs.com/stone41123/p/7581290.html
Copyright © 2011-2022 走看看