• 堆以及一些用法 QWQ这是写得最认真的板子题


    最近一直在学图论,然后吧,由于学的东西实在是太多太杂了,加上蒟蒻本蒻又经常颓,所以落了好多好多板子题的整理没写啊嘤嘤嘤,不过把这些东西学的差不多了,再一块写个整理,其实感觉还不错?????也算是很神奇吧,大概就是知识的积淀这一块有了一点用

    好了,话不多说,我们来进入正题

    P3378 【模板】堆

    我们从板子题入手,慢慢的了解堆(其实是自己巩固而已QWQ)

    这里我们学习的堆其实是二叉堆,算是比较狭义的一种定义吧,首先,

    堆是一种特殊的二叉树,而且是完全二叉树

    为什么我们接触到的比较早的数据结构是堆呢?

    其实有这么几个原因:

    1.因为堆是一颗完全二叉树,所以在父亲和儿子方面,就完全不需要结构体存树,而是严格按照完全二叉树的定义来就可以

    一棵树上的一个节点i,他的儿子是i*2和i*2+1,他的父亲是i/2(此处为整除)

    所以处理的时候就很方便啊。

    2.c++自带的STL对于堆非常友好,基本上随便写几个STL就能构建一个堆,即使是手写,大约时间也就是十分钟左右,并不是太慢。

    3.堆的应用不少,比如堆排序,以及了解一种数据结构,为将来学习可并堆,斐波那契堆打下坚实基础,而且能优化dijkstra(图论).



    下面来讲一讲堆的一些基本操作步骤

    插入(大雾

    我们要对堆插入一个元素,但是并不是扔进去,让尾指针++就完事了,我们还得对堆的正确性进行维护。

    来说一下思想,对于添加进来的元素,设其位置为now,那么他爹就是now/2(now>>1  位运算更快哦)

    只要比较二者大小,如果新元素更小,那么就交换即可,否则意味着合法,我们直接退出循环就可以,循环的终止条件就是now!=1(因为当now==1时,它已经是堆首元素,没爹。。。。多苦的一孩子(大雾)

    来看代码

    inline void add(int x)
    {
        Heap[++cnt] = x;//这里小小的压了一下行
        int now = cnt;
        while (now!=1)
        {
            if (Heap[now] < Heap[now >> 1])
                swap(Heap[now], Heap[now >> 1]),now>>=1;
            else
                break;
        }
    }

    弹出

    这个也要分为两种,一种是弹出堆首元素,另一种是弹出任意位置的元素(这种一般与寻找元素相结合,考察对DFS,BFS之类的搜索方法的能力)

    先看弹出堆首元素,

    想要弹出的话,我们就把最小值修改为INF(我比较喜欢1e9),然后和之前相反,向下比较直到找到合适为止为止。

    具体讲一讲

    因为小根堆的要求是所有根节点都得比他孩子小,所以我们定义首节点的位置为root=1,因为已经置成INF了,所以我们向下开始比较;

    先让两个孩子比,最小的那个再和root比,如果比root小,那么就交换二者,否则符合条件就直接退出循环,这里的循环终止条件是   root << 1 <= cnt,也就是说root的儿子已经比当前的堆的长度大了,也就是不存在儿子了

    但是这种方法其实不是很好啊,因为你排到最后,最底下就一大堆INF,难看的要死还占空间,倒不如直接交换首元素和尾元素,然后直接把尾指针减一就可以,这样的话最小值就被删除了,之后进行一下动态维护就可以。

    来看代码

    inline void pop()
    {
        Heap[1] = Heap[cnt--];
        int root = 1;
        while (root << 1 <= cnt)
        {
            int son;
            if ((root << 1) + 1 > cnt ||
                      Heap[root << 1] < Heap[(root << 1) + 1])
            {
                son = root << 1;
            }
            else
                son = (root << 1) + 1;
            if (Heap[son] > Heap[root])
                break;
            swap(Heap[root], Heap[son]);
            root = son;
        }
    }

    输出堆首元素

    这东西其实没啥好讲的,因为堆首元素就肯定是Heap[1]嘛,知道就行,然后就可以输出了,因为不对堆中元素进行移动和修改,是不影响堆的合法性的。

    这些都看的差不多了,就直接略微修改,板子题就切掉啦

    #include <iostream>
    #include <queue>
    #include <algorithm>
    #include <cstdio>
    using namespace std;
    const int INF = 214748367;
    int n, a, b, Heap[1000001], cnt = 0;
    inline void add(int x)
    {
        Heap[++cnt] = x;
        int now = cnt;
        while (now!=1)
        {
            if (Heap[now] < Heap[now >> 1])
                swap(Heap[now], Heap[now >> 1]),now>>=1;
            else
                break;
        }
    }
    inline void print()
    {
        printf("%d
    ", Heap[1]);
    }
    inline void pop()
    {
        Heap[1] = Heap[cnt--];
        int root = 1;
        while (root << 1 <= cnt)
        {
            int son;
            if ((root << 1) + 1 > cnt ||
                      Heap[root << 1] < Heap[(root << 1) + 1])
            {
                son = root << 1;
            }
            else
                son = (root << 1) + 1;
            if (Heap[son] > Heap[root])
                break;
            swap(Heap[root], Heap[son]);
            root = son;
        }
    }
    int main()
    {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i)
        {
            scanf("%d", &a);
            if (a == 1)
            {
                scanf("%d", &b);
                add(b);
            }
            if (a == 2)
                print();
            if (a == 3)
                pop();
        }
        return 0;
    }

    还是肥肠感谢gh神仙的热心帮忙啊,,,,QWQ一个板子题交了三四遍才过,真的是太辣鸡了

    看完板子题,我们来看看合并果子这个题

    P1090 合并果子

    这个题在学完堆之后就好做多了,不过蒟蒻我很早以前做的时候是用dp加快排做的,那叫一个惨不忍睹啊,,,,,,,,连个样例都没过,所以干脆也就没有提交记录了

    但是我们要分析分析,为什么这种算法会TLE

    思考一下,我们对于,每一堆果子的个数进行排序,这样的话,先合并前两个果子,看似没什么问题,但是当你合并完了之后,你又得干啥呢???

    我当时就傻乎乎的又用sort,然后就连本地编译都会超时,我们分析一下时间复杂度,其实就是O(nlongn+(n-1)log(n-1)+(n-2)log(n-2)+.....+log 1)

    这样的话就肥肠慢了啊,因为每一次快排都是遍历了所有的数,但是其实很大一部分都是有序的,所以快排也没什么用,反而浪费时间。

    那么用堆解决就没有任何问题了

    每次合并前两个小的集合,然后把合并完的集合加到ans中,再扔回堆进行维护,这道题就做完啦

    上代码

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    using namespace std; 
    int n,ans,Heap[10010],num,cnt=0,sum;
    inline void add(int x)
    {
        Heap[++cnt] = x;
        int now = cnt;
        while (now!=1)
        {
            if (Heap[now] < Heap[now >> 1])
                swap(Heap[now], Heap[now >> 1]),now>>=1;
            else
                break;
        }
    }
    inline void pop()
    {
        Heap[1] = Heap[cnt--];
        int root = 1;
        while (root << 1 <= cnt)
        {
            int son;
            if ((root << 1) + 1 > cnt ||
                      Heap[root << 1] < Heap[(root << 1) + 1])
            {
                son = root << 1;
            }
            else
                son = (root << 1) + 1;
            if (Heap[son] > Heap[root])
                break;
            swap(Heap[root], Heap[son]);
            root = son;
        }
    }
    int main()
    {
       
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&num);
            add(num);
        }
        while(cnt>=2)
        {
            sum+=Heap[1];
            pop();
            sum+=Heap[1];
            pop();
            ans+=sum;
            add(sum);
            sum=0;
        }
        printf("%d",ans);
        return 0;
    }

    想看堆排的同学看这里

  • 相关阅读:
    python utf8/gbk/unicode 编码及解码
    用python 实现一个在linux上修改某行配置文件的配置信息
    docker 部署elasticsearch(ES)
    python 进行es查询
    python kafka(一)生产和消费
    linux Centos 上安装docker
    python 进行es聚合查询
    关系型数据库简介(mariadb)
    git revert
    分支合并代码
  • 原文地址:https://www.cnblogs.com/this-is-M/p/10789574.html
Copyright © 2020-2023  润新知