简单堆结构详解

  堆的本质就是一棵完全二叉树。

  简单的堆可以分为两种:大顶堆小顶堆

0.为什么选择堆


  在OI中,我们经常遇到一类问题,需要重复寻找最大值或最小值如果我们每次都遍历一遍数组或每次都 sort 一遍,时间复杂度会非常之高,有可能出现 O(n2的悲剧。这时候,亲爱的堆就出现了。

  堆只需要通过更改部分关系,来维护整个堆结构。

  堆有一个优秀的时间复杂度——O(n log n)

  老师们常说,带log的都很厉害……

1.大顶堆与小顶堆的定义


  顾名思义,当我们在构建二叉堆时,如果最大值在root( 树根,tree[1] ) 上,并且堆内父亲的值都大于等于儿子的值,就是大顶堆(顶部值最大);

  如果最小值在root上,并且堆内父亲的值都小于等于儿子的值,就是小顶堆(顶部值最小)。

2.堆的基本操作


  堆有两个基本操作——put(data)存入值,get()获得堆的顶部值(root)。

2.1建立大顶堆

1 while(n--){
2     scanf("%d",&data);
3     put(data);
4 }

  put函数需要实现:

  1. 在堆尾增加一个值data;
  2. 维护堆的关系

  如何维护堆的结构:

  1. 从堆尾data处,沿着二叉树一级一级向上找,不断更新父子关系,直到堆关系正确。
 1 void put(int data){
 2     int ch,fa;//child,father
 3     heap[++heap_size]=data;//heap_size为全局变量 堆内元素个数
 4     ch = heap_size;//从堆尾开始查找 
 5     while(ch>1){//child不为根 
 6         fa = ch / 2;//父亲位置 
 7         if(heap[ch] <= heap[fa])break;//如果child不够大,就没必要冒上去
 8         swap(heap[ch],heap[fa]);//否则交换二值 
 9         ch = fa;//更新 
10     }
11 }

  如此而来就能保证,堆中的父亲一定比儿子大了。

  如果你不能理解,那么请看图:

  我们的堆中已经有了[5,3,2,1,1],我们现在增加一个元素[6]至堆尾

 first

  此时child=6,所以fa=3,比较更新父子关系:

Second

  此时child=3,,所以fa=1,再次比较更新父子关系:

       Third

  此时child已经等于一了,所以我们不在继续更新父子关系。

  现在,这个结构已经成为了一个完美的大顶堆。

 2.2.获得堆顶值并维护大顶堆

   get函数目标效果

  1. 取出堆顶值并将其删除;
  2. 维护大顶堆关系

  Code:

 1 int get(){
 2     int fa,ch;
 3     int ans = heap[1];
 4     heap[1] = heap[heap_size--];//便捷方式:用堆尾的值覆盖堆顶值 
 5     fa = 1;
 6     while(fa*2<=heap_size){
 7         ch = fa*2;
 8         if(ch < heap_size && heap[ch+1] > heap[ch])//选择两个孩子中更大的那一个 
 9             ch++;
10         if(heap[p]>=heap[child])break; //father已经够大了,不需要再继续向下找
11         swap(heap[ch],heap[fa]);
12         fa = ch;//更新 
13     }
14     return ans;
15 }

  如图所示:

 

2.3.小顶堆

  原理与大顶堆相同,所以——

  咕。

 3.例题:合并果子


 

3.1.题目描述

  在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

  每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n-1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 1 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有 3 种果子,数目依次为 1 , 2 , 9 。可以先将 、 2 堆合并,新堆数目为 3 ,耗费体力为 3 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12 ,耗费体力为 12 。所以多多总共耗费体力 =3+12=15 。可以证明 15 为最小的体力耗费值。

  题目链接

3.2.思路

  贪心的想法:每次都合并第一小与第二小的数据,然后将合并后的数据加入数据中等待下次合并。

  所以

  我们可以考虑小根堆算法

3.3.代码

  Code:

 1 #include<cstdio>
 2 #include<iostream>
 3  
 4 using namespace std;
 5  
 6 int n,h,a[10001];
 7  
 8 void put(int c){//小根堆维护 
 9     int p1,p2;
10     h++;
11     a[h]=c;//堆内元素个数 
12     p1=h;
13     while(p1>1){
14         p2=p1/2;//p2父亲位置 
15         if(a[p1]>=a[p2])return ;//不够小 
16         swap(a[p1],a[p2]);
17         p1=p2;
18     }
19 }
20  
21 int get(){
22     int p=1,next,ans;
23     ans=a[1];
24     a[1]=a[h];
25     h--;
26     while(p*2<=h){
27         next=p*2;
28         if(next<h and a[next+1]<a[next])next++;//选择较小的孩子 
29         if(a[p]<=a[next])break;//已经够小 
30         swap(a[p],a[next]);
31         p=next;
32     }
33     return ans;
34 }
35  
36 int main(){
37     int c,ans=0;
38     scanf("%d",&n);
39     for(int i=1;i<=n;i++){
40         scanf("%d",&c);
41         put(c);
42     }
43     while(h>1){
44         int one=get();
45         int two=get();
46         put(one+two);
47         ans+=(one+two);
48     }
49     printf("%d",ans);
50     return 0;
51 }

 

4.口胡:堆排序


 

从大到小排序:大顶堆

从小到大排序:小顶堆

 1 #include<cstdio>
 2 #include<iostream>
 3  
 4 using namespace std;
 5  
 6 int a[100001],h;
 7  
 8 void put(int c){
 9     int p1,p2;
10     h++;
11     a[h]=c;
12     p1=h;
13     while(p1>1){
14         p2=p1/2;
15         if(a[p2]<=a[p1])return;
16         swap(a[p1],a[p2]);
17         p1=p2;
18     }
19 }
20  
21 int get(){
22     int p=1,next,ans;
23     ans=a[1];
24     a[1]=a[h];
25     h--;
26     while(p*2<=h){
27         next=p*2;
28         if(next<h&&a[next+1]<a[next])next++;
29         if(a[p]<a[next])break;
30         swap(a[p],a[next]);
31         p=next;
32     }
33     return ans;
34 }
35  
36 int main(){
37     int n;
38     scanf("%d",&n);
39     for(int i=1;i<=n;i++){
40         int t;
41         cin>>t;
42         put(t);
43     }
44     for(int i=1;i<=n;i++){
45         printf("%d ",get());
46     }
47     return 0;
48 }

 

  时间复杂度:O(2×n log n)


 

  -------------The END-----------------

posted @ 2020-08-30 09:30  AlienCollapsar  阅读(467)  评论(0编辑  收藏  举报
// 生成目录索引列表 // ref: http://www.cnblogs.com/wangqiguo/p/4355032.html // modified by: zzq