STL 堆heap的用法

STL中并没有把heap作为一种容器组件,heap的实现亦需要更低一层的容器组件(诸如list,array,vector)作为其底层机制。Heap是一个类属算法,包含在algorithm头文件中。虽然STL中关于heap默认调整成的是大顶堆,但却可以让用户利用自定义的compare_fuction函数实现大顶堆或小顶堆。heap的低层机制vector本身就是一个类模板,heap基于vector便实现了对各种数据类型(无论基本数据类型还是用户自定义的数据类型)的堆排(前提是用户自定义的数据类型要提供比较机制compare_fuction函数)。

STL里面的堆操作一般用到的只有4个。


他们就是

make_heap();、pop_heap();、push_heap();、sort_heap();

他们的头函数是algorithm

首先是make_heap();

他的函数原型是:

void make_heap(first_pointer,end_pointer,compare_function);

一个参数是数组或向量的头指针,第二个向量是尾指针。第三个参数是比较函数的名字
。在缺省的时候,默认是大跟堆。(下面的参数都一样就不解释了)

作用:把这一段的数组或向量做成一个堆的结构。范围是(first,last)

然后是pop_heap();

它的函数原型是:

void pop_heap(first_pointer,end_pointer,compare_function);

作用:pop_heap()不是真的把最大(最小)的元素从堆中弹出来。而是重新排序堆。它
把first和last交换,然后将[first,last-1)的数据再做成一个堆。

接着是push_heap()

void pushheap(first_pointer,end_pointer,compare_function);

作用:push_heap()假设由[first,last-1)是一个有效的堆,然后,再把堆中的新元素加
进来,做成一个堆。

最后是sort_heap()

void sort_heap(first_pointer,end_pointer,compare_function);

作用是sort_heap对[first,last)中的序列进行排序。它假设这个序列是有效堆。(当然
,经过排序之后就不是一个有效堆了)

 

 
  1. 下面是例程:

  2.  
  3. #include<algorithm>

  4.  
  5. #include<cstdio>

  6.  
  7. using namespace std;

  8.  
  9. bool cmp(int a,int b) //比较函数

  10.  
  11. {

  12.  
  13. return a>b;

  14.  
  15. }

  16.  
  17. int main()

  18.  
  19. {

  20.  
  21. int i,number[20]={29,23,20,22,17,15,26,51,19,12,35,40};

  22.  
  23. make_heap(&number[0],&number[12]);

  24.  
  25. //结果是:51 35 40 23 29 20 26 22 19 12 17 15

  26.  
  27. for(i=0;i<12;i++)

  28.  
  29. printf("%d ",number[i]);

  30.  
  31. printf("\n");

  32.  
  33. make_heap(&number[0],&number[12],cmp);

  34.  
  35. //结果:12 17 15 19 23 20 26 51 22 29 35 40

  36.  
  37. for(i=0;i<12;i++)

  38.  
  39. printf("%d ",number[i]);

  40.  
  41. printf("\n");

  42.  
  43. //加入元素8

  44.  
  45. number[12]=8;

  46.  
  47. //加入后调整

  48.  
  49. push_heap(&number[0],&number[13],cmp);

  50.  
  51. //结果:8 17 12 19 23 15 26 51 22 35 40 20

  52.  
  53. for(i=0;i<13;i++)

  54.  
  55. printf("%d ",number[i]);

  56.  
  57. printf("\n");

  58.  
  59. //弹出元素8

  60.  
  61. pop_heap(&number[0],&number[13],cmp);

  62.  
  63. //结果:12 17 15 19 23 20 26 51 22 29 35 40

  64.  
  65. for(i=0;i<13;i++)

  66.  
  67. printf("%d ",number[i]);

  68.  
  69. printf("\n");

  70.  
  71. sort_heap(&number[0],&number[12],cmp);

  72.  
  73. //结果不用说都知道是有序的了!

  74.  
  75. for(i=0;i<12;i++)

  76.  
  77. printf("%d ",number[i]);

  78.  
  79. return 0;

  80.  
  81. }

原文  http://blog.csdn.net/jxh_123/article/details/34853099

说明 :本文仅供学习交流,转载请标明出处,欢迎转载!

       堆(heap)是一种非常重要的数据结构(这里我们讨论的是 二叉堆 ),它是一棵满足特定条件的完全二叉树, 堆的定义 如下:

       堆是一棵树完全二叉树,对于该完全二叉树中的每一个结点x,其关键字大于等于(或小于等于)其左右孩子结点,而其左右子树均为一个二叉堆。

        在上述的定义中,若堆中父亲结点关键字的值大于等于孩子结点,则称该堆为大顶堆 ;若堆中父亲结点关键子的值小于等于孩子结点,则称该堆为 小顶堆 。

        由于 堆是一棵完全二叉树 ,所以我们可以很轻易地用一个数组存储堆中的每一个元素,并且由子结点访问到其父亲结点和由父亲结点访问到其子结点。下面给出图来说明该表示方法:

          下面我们给出学数据结构时堆数组进行堆排序的整个过程:建堆、出堆、向上调整、向下调整等过程。

  1. 堆排序(大顶堆)//   
  2.   
  3. void  HeapAdjustDown( int  *arr, int  length, int  i) //对第i个值做向下调整,i=0,...,length-1   
  4. {  
  5.      if (arr==NULL || length<=0 || i<0)  
  6.     {  
  7.          return  ;  
  8.     }  
  9.      int  temp=arr[i];  
  10.      int  j; //i相当于前驱,j相当于后继   
  11.      for (j=2*i+1;j<length;) //即j<=length-1   
  12.     {  
  13.          if (j+1<length && arr[j]<arr[j+1]) //如果存在右孩子,且右孩子值待遇   
  14.         {  
  15.             j++; //与右孩子交换   
  16.         }  
  17.          if (arr[j]<=temp) //如果左右孩子中的较大值小于该结点值,则说明无需向下调整了   
  18.         {  
  19.              break ;  
  20.         }  
  21.          else //如果左右孩子中的较大值大于该结点值,则想到直接插入排序   
  22.         {  
  23.             arr[i]=arr[j]; //孩子结点值中较大的上移动   
  24.             i=j; //i用于追踪待插入点的位置   
  25.             j=2*i+1;  
  26.         }  
  27.     }  
  28.      if (arr[i]!=temp) //如果i的值发生了移动   
  29.     {  
  30.         arr[i]=temp; //将最初第i个节点值放入合适的位置   
  31.     }  
  32.   
  33. }  
  34. void  CreateHeap( int  *arr, int  length) //创建一个堆   
  35. {  
  36.      if (arr==NULL || length<=0)  
  37.     {  
  38.          return  ;  
  39.     }  
  40.      int  i;  
  41.      for (i=(length-2)/2;i>=0;i--) //最后一个元素的序号为length-1,故该结点的父结点为(length-2)/2   
  42.     {  
  43.         HeapAdjustDown(arr,length,i); //从倒数第二行开始倒着向下调整   
  44.     }  
  45. }  
  46. void  HeapDelete( int  *arr, int  len) //删堆顶元素,len表示当前表的长度   
  47. {  
  48.      if (arr==NULL || len<=0 || len==1) //len=1,表示当堆中只有一个元素时,无需排序   
  49.     {  
  50.          return  ;  
  51.     }  
  52.      else   
  53.     {  
  54.         swap(arr[0],arr[len-1]);  
  55.         HeapAdjustDown(arr,len-1,0); //删除后,数组长度减1了   
  56.     }  
  57. }  
  58.   
  59. void  HeapSort( int  *arr, int  length) //堆排序   
  60. {  
  61.      if (arr==NULL || length<=1)  
  62.     {  
  63.          return  ;  
  64.     }  
  65.     CreateHeap(arr,length); //先创建一个堆0   
  66.      int  i;  
  67.      for (i=0;i<=length-1;i++) //删除length-1次即可,因为最后一个元素就剩自己了   
  68.     {  
  69.         HeapDelete(arr,length-i);  
  70.     }  

       STL的魅力之一在于能够对特定类型的数据结构提供泛型化,并且提供高效的函数接口。没错,STL不仅实现了上述算法,而且还弥补上述算法的不足。

        从上面的代码中,我们发现的不足之处在于:整个过程都是针对一个静态数组,静态数组在插入和删除方面表现的特别笨。所以,STL以动态数组vector为底层实现,提供了几个重要的关于堆的几个重要操作,这些操作分别是:建堆操作、插入操作、删除操作、堆排序操作,下面分别给出这几种操作的函数原型和相关说明。

建堆算法 : inline void make_heap( b, e , cmp=greater<T>() )

        该函数对[b,e)范围中的元素建立一个堆,所建的堆的类型由cmp决定,默认为大顶堆。

堆插入算法 : inline void push_heap(b,e,cmp=greater<T>() )

        向堆中插入元素分为两个步骤:

       (1)先将待插入的元素插入到底层容器的末端,通过 push_back 函数实现。

       (2)再调用 push_heap(b,e,cmp) 函数堆新插入的元素做向上调整。

          所以,调用push_heap函数之前,先要保证待插入的元素已经放到了原容器的末尾,否则push_heap就做了无用功。

堆假删除算法 : inline void pop_heap(b,e,cmp=greater<T>() )

        要实现堆的真正删除操作,分两步进行:

      (1)先调用pop_heap函数将首部的元素与尾部元素交换,再将原尾部的元素做向下调整操作。此时,原堆顶元素被放置在最后一个位置,并未从底层容器中删除。

      (2)若要实现真正的元素删除,可以调用底层容器的pop_back函数。

         所以,在调用pop_heap函数后,若要实现元素真正从堆中删除,还需要调用底层容器的pop_back函数。

堆排序算法 : inline void sort_heap(b,e,cmp=greater<T>() )

       根据上面的堆排序代码,我们可以看出,堆排序实际上是 对堆中元素不断地假删除操作,只不过在删除过程中,[b,e)中的e每删除一次,就要做--e的更新 。

        总结,以上的几个函数中,都要注意以下几个问题:

        1.所以的[b,e)中的b和e都是 随机访问迭代器(RandomAccessIterator) ,根据这一性质,我们可以得出,在我们之前提过的三个基础顺序容器中, 只有vector和deque可以作为堆的底层实现容器,而list容器不能作为底层实现容器 ,因为list容器内置的迭代器为随机访问迭代器。

        2.上述函数都含有第三个参数,该参数决定了堆的类型(大顶堆、小顶堆) ,默认情况下,该堆的类型为大顶堆 。如果我们 要使用小顶堆,则之后所有的以上函数的调用都必须加上对应的参数 ,否则结果就会出错。

        3.以上函数的第三个参数,我们有两种给出实参的方法:

      (1)采用仿函数对象 ,这是C++的习惯,对应的头文件为: #include<functional>。 注意是仿函数对象,不是仿函数类型, 对象只出现在函数内部,类型则只出现在模板内部 , 仿函数都是inline函数,所以其效率要高于(2) 。

        例如:  小顶堆------->greater<T>()     大顶堆------------------>less<T>()

      (2)采用自定义函数名,并将该函数名传递给第三个参数,具体如下:

bool less_cmp(const int &a,const int &b)//等价于: less<int>()
{
  return a<b;
}

bool greater_cmp(const int &a, const int &b) //等价于: greater<int>()
{
  return a>b;
}

            最后,我们给出一个小顶堆的测试程序及其输出结果:

 
  1. #include<iostream>

  2. #include<vector>

  3. #include<list>

  4. #include<algorithm>//后面用到copy函数和heap相关函数

  5. #include<iterator>//后面用到迭代器ostream_iterator

  6. #include<functional>//后面用到了一个比较的仿函数greater<T>

  7. using namespace std;

  8. typedef vector<int> Vint;

  9. void print(const Vint& vec)//输出当前vector容器中的元素

  10. {

  11. cout<<"容器内的元素为:";

  12. copy(vec.begin(),vec.end(),ostream_iterator<int>(cout," "));//将容器内的元素输出到标准输出设备上

  13. cout<<endl;

  14. cout<<"容器内元素的个数为:"<<vec.size()<<endl<<endl;;

  15. }

  16.  
  17. bool cmp(const int &a,const int &b)

  18. {

  19. return a>b;//大顶堆,则cmp相等于greater<int>(),注意不是greater<int>,前者是一个对象,后者是一个类

  20. }

  21. int main()

  22. {

  23. int arr[]={3,2,1,9,4,12,15,7};

  24. vector<int>vec(arr,arr+sizeof(arr)/sizeof(int));//创建一个vector容器对象,将数组的副本压入到该容器中

  25. cout<<"-----------初始状态---------------"<<endl;

  26. print(vec);//将最初的vector容器的内容输出

  27.  
  28. cout<<"-------------建堆----------------"<<endl;

  29. make_heap(vec.begin(),vec.end(),cmp);//新建一个小顶堆

  30. //上行代码等价于make_heap(vec.begin(),vec.end(),greater<int>()

  31. print(vec);

  32.  
  33. cout<<"----------弹出堆顶元素-----------"<<endl;

  34. pop_heap(vec.begin(),vec.end(),cmp);//这里也要加cmp,因为弹出之后要给出向下调整的规则,否则系统会调用默认的最大堆调整方法

  35. print(vec);

  36.  
  37. cout<<"--------向堆中插入值6的方法--------"<<endl;

  38. vec.push_back(6);//先将待插入的值放在容器的末尾

  39. push_heap(vec.begin(),vec.end(),cmp);//再最堆进行向下调整

  40. print(vec);

  41.  
  42. cout<<"----------执行堆排序--------------"<<endl;

  43. sort_heap(vec.begin(),vec.end(),cmp);

  44. print(vec);

  45. return 0;

  46. }

             测试结果如下:

 

 

操作①:make_heap(a.begin(),a.end()) 顾名思意,创建一个堆,默认为最大堆,可以加第三个参数修改。
操作②:pop_heap(a.begin(),a.end()) 删除头结点,实际上是将头和尾互换位置,以(a.begin(),a.end()-1)为区间重新构建堆,需要手动删除最后一个元素(a.pop_back());
操作③:push_heap(a.begin(),a.end()) 往堆中增加一个元素,在这操作之前需要先将元素压入容器(a.push_back())
操作④:sort_heap(a.begin(),a.end())堆排序。(没用过)

原文:https://blog.csdn.net/zsc2014030403015/article/details/45872737

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值