堆排序(优先队列)——合并果子

原创 2016年08月29日 22:01:27

首先说一下堆的性质:分为大根堆和小根堆,根节点都是最值,小根堆的根节点是最小的,每个堆都比它的两个子堆要小,大根堆的根节点是最大的,每个堆都比它的两个子堆要大。

顺便说一下二叉树的性质,左子树小于根节点小于右子树,所以都要先序遍历。

合并果子,是指有n堆果子,每次合并两堆,每次花费的力气为两堆之和,求合并为一堆后,花费的最小力气。

显然,每次合并最小的两堆即可满足题意。

又显然,要用堆排序,这里用小根堆。

(PS:堆跟二叉树一样,都是一个一维数组,每个点的左子树(左堆)的编号等于它*2,右子树(右堆)的编号等于它*2+1)

堆和树的优势:不需要像队列一样一个个枚举 而是每次判断将剩余情况二分,能将O(N)变成O(logN)。(二分的思路有很多 堆和树还有快排)

显然二分的前提是队列要有某种规律,比如需要先排好序。

比如一个数列1,2,3,4,5,6,7,8,9,10;(a[1]~a[10])

比如要查找8;

倘若枚举,显然要8次;

如果二分,一开始的范围显然是全部(1~10)。

取中间a[(1+10)/2]=a[5]=5<8;

那么范围变为6~10。(一次查找)

继续查找,在剩余范围二分。

a[(6+10)/2]=a[8]=8。找到目标(两次查找)

这就是二分的优点。

当然,倘若要寻找1,枚举会比二分快,但二分的优势是整体优化,也就是说,在刚刚的样例里,显然所有的数在4次以内都会被搜到,而枚举,最多需要10次。

下面上代码:

#include<iostream>
#include<cstdio>
using namespace std;
long long ans;
int a[110000],len,s;
int main(){
    int i,j,k;
    int n,u;
    cin>>n;
    for(i=1;i<=n;i++){
        cin>>a[i];//每次读入一个堆,存在队尾,因为直接在最低层,可能会比之前的数小,不满足堆的性质,就开始排序(往上浮)
        u=i;
        while(a[u]<a[u/2] && u>1){//如果它比它的根堆还小 并且它不是根堆(若它是根堆即a[1]那么它的父堆为a[0]=0 所以需要特判)
            k=a[u];
            a[u]=a[u/2];
            a[u/2]=k;
            u/=2;//交换位置 u指新插入的数在数组中的位置
        }
    }
    len=n;
    while(len>1){
        s=a[1];//每次取前两堆求和,先取出第一堆
        a[1]=a[len];//将第一堆直接赋值成最后一堆
        len--;//取出了一堆 所以长度-1
        u=1;//将最后一堆变成了第一堆 很可能不满足堆的性质 此时堆的编号为1 可能需要往下沉
        while((a[u*2]<a[u] && u*2<=len) || (a[u*2+1]<a[u] && u*2+1<=len)){//若它比它的子堆大 并且它的子堆未超出长度(之前删除的堆没有覆盖 只是将长度-1了所以数组后面的值仍然在那)
            if(a[u*2]>a[u*2+1] && len>=u*2+1)u=u*2+1;//优先往小的那边沉
            else u*=2;
            k=a[u];
            a[u]=a[u/2];
            a[u/2]=k;//交换
        }
        s+=a[1];//之前已经取出过一堆了 现在重新使堆满足性质后 取出第二堆相加
        a[1]=s;//将取出的两堆合起来放回去,显然此时可能不满足堆的性质了,需要往下沉
        ans+=s;//将花费的力气加入ans
        u=1;//此时可能需要下沉的堆的编号为1
        while((a[u*2]<a[u] && u*2<=len) || (a[u*2+1]<a[u] && u*2+1<=len)){
            if(a[u*2]>a[u*2+1] && len>=u*2+1)u=u*2+1;
            else u*=2;
            k=a[u];
            a[u]=a[u/2];
            a[u/2]=k;//交换
        }
    }
    cout<<ans;//输出
    return 0;
}


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

NOIp 2004 合并果子

题目描述         在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。          每一次合并,多多可以把两堆果子合并到一起...

Tyvj 1066 合并果子(贪心)

时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 NOIP2004 提高组 第二道 描述     在一个果园里,多多已经将所...

合并果子

为了防止抄袭代码,本人只供应伪代码 1.题目: 合并果子 成绩 10 开启时间 2013年03月19日 星期二 14:40 折扣 0.8 折扣时间 2013年03月28...

合并果子

题目大意有n堆果子数目,需要合并成一堆。合并两堆果子需要的体力为两堆果子数目之和,求所需的最小体力值。输入3 1 2 9输出15思路此题时间复杂度O(nlog n)。构建一棵小根堆,每次把堆顶...

SDUT OJ 树-堆结构练习——合并果子之哈夫曼树 C++优先队列练习

今天做题跟同学学习了优先队列,瞬间感觉不错哦。就记下来了。。。以后复习用。。。。 题目描述  在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合...

堆排序与优先队列

《常见算法和数据结构》优先队列(3)——堆排序

堆排序1堆排序思想堆排序的思想很简单,就是利用了堆的优点,不断的找最大值,然后删除最大值,重复操作。 - 首先,要构造一个二叉堆出来,我们采用自底向上的思想。 做删除root的操作(但是不是真删除,...

合并果子(贪心+优先队列)

C - 合并果子 Crawling in process...Crawling failedTime Limit:1000MS Memory Limit:131072KB 64bit IO Form...

【vijos】【优先队列】合并果子

贪心每次选最小的两堆合并即可。 注意STL自带的优先队列是大根堆,排序是降序排列,升序需要把缺省的比较函数换成greater(也是STL自带)#include #include using na...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)