STL中并没有把heap作为一种容器组件,heap的实现亦需要更低一层的容器组件(诸如list,array,vector)作为其底层机制。Heap是一个类属算法,包含在algorithm头文件中。虽然STL中关于heap默认调整成的是大顶堆,但却可以让用户利用自定义的compare_fuction函数实现大顶堆或小顶堆。heap的低层机制vector本身就是一个类模板,heap基于vector便实现了对各种数据类型(无论基本数据类型还是用户自定义的数据类型)的堆排(前提是用户自定义的数据类型要提供比较机制compare_fuction函数)。
STL里面的堆操作一般用到的只有4个。
下面是例程:
#include
#include
using namespace std;
bool cmp(int a,int b) //比较函数
{
return a>b;
}
int main()
{
int i,number[20]={29,23,20,22,17,15,26,51,19,12,35,40};
make_heap(&number[0],&number[12]);
//结果是:51 35 40 23 29 20 26 22 19 12 17 15
for(i=0;i<12;i++)
printf("%d ",number[i]);
printf("\n");
make_heap(&number[0],&number[12],cmp);
//结果:12 17 15 19 23 20 26 51 22 29 35 40
for(i=0;i<12;i++)
printf("%d ",number[i]);
printf("\n");
//加入元素8
number[12]=8;
//加入后调整
push_heap(&number[0],&number[13],cmp);
//结果:8 17 12 19 23 15 26 51 22 35 40 20
for(i=0;i<13;i++)
printf("%d ",number[i]);
printf("\n");
//弹出元素8
pop_heap(&number[0],&number[13],cmp);
//结果:12 17 15 19 23 20 26 51 22 29 35 40
for(i=0;i<13;i++)
printf("%d ",number[i]);
printf("\n");
sort_heap(&number[0],&number[12],cmp);
//结果不用说都知道是有序的了!
for(i=0;i<12;i++)
printf("%d ",number[i]);
return 0;
}
说明 :本文仅供学习交流,转载请标明出处,欢迎转载!
堆(heap)是一种非常重要的数据结构(这里我们讨论的是 二叉堆 ),它是一棵满足特定条件的完全二叉树, 堆的定义 如下:
堆是一棵树完全二叉树,对于该完全二叉树中的每一个结点x,其关键字大于等于(或小于等于)其左右孩子结点,而其左右子树均为一个二叉堆。
在上述的定义中,若堆中父亲结点关键字的值大于等于孩子结点,则称该堆为大顶堆 ;若堆中父亲结点关键子的值小于等于孩子结点,则称该堆为 小顶堆 。
由于 堆是一棵完全二叉树 ,所以我们可以很轻易地用一个数组存储堆中的每一个元素,并且由子结点访问到其父亲结点和由父亲结点访问到其子结点。下面给出图来说明该表示方法:
下面我们给出学数据结构时堆数组进行堆排序的整个过程:建堆、出堆、向上调整、向下调整等过程。
STL的魅力之一在于能够对特定类型的数据结构提供泛型化,并且提供高效的函数接口。没错,STL不仅实现了上述算法,而且还弥补上述算法的不足。
从上面的代码中,我们发现的不足之处在于:整个过程都是针对一个静态数组,静态数组在插入和删除方面表现的特别笨。所以,STL以动态数组vector为底层实现,提供了几个重要的关于堆的几个重要操作,这些操作分别是:建堆操作、插入操作、删除操作、堆排序操作,下面分别给出这几种操作的函数原型和相关说明。
建堆算法 : inline void make_heap( b, e , cmp=greater
该函数对[b,e)范围中的元素建立一个堆,所建的堆的类型由cmp决定,默认为大顶堆。
堆插入算法 : inline void push_heap(b,e,cmp=greater
向堆中插入元素分为两个步骤:
(1)先将待插入的元素插入到底层容器的末端,通过 push_back 函数实现。
(2)再调用 push_heap(b,e,cmp) 函数堆新插入的元素做向上调整。
所以,调用push_heap函数之前,先要保证待插入的元素已经放到了原容器的末尾,否则push_heap就做了无用功。
堆假删除算法 : inline void pop_heap(b,e,cmp=greater
要实现堆的真正删除操作,分两步进行:
(1)先调用pop_heap函数将首部的元素与尾部元素交换,再将原尾部的元素做向下调整操作。此时,原堆顶元素被放置在最后一个位置,并未从底层容器中删除。
(2)若要实现真正的元素删除,可以调用底层容器的pop_back函数。
所以,在调用pop_heap函数后,若要实现元素真正从堆中删除,还需要调用底层容器的pop_back函数。
堆排序算法 : inline void sort_heap(b,e,cmp=greater
根据上面的堆排序代码,我们可以看出,堆排序实际上是 对堆中元素不断地假删除操作,只不过在删除过程中,[b,e)中的e每删除一次,就要做--e的更新 。
总结,以上的几个函数中,都要注意以下几个问题:
1.所以的[b,e)中的b和e都是 随机访问迭代器(RandomAccessIterator) ,根据这一性质,我们可以得出,在我们之前提过的三个基础顺序容器中, 只有vector和deque可以作为堆的底层实现容器,而list容器不能作为底层实现容器 ,因为list容器内置的迭代器为随机访问迭代器。
2.上述函数都含有第三个参数,该参数决定了堆的类型(大顶堆、小顶堆) ,默认情况下,该堆的类型为大顶堆 。如果我们 要使用小顶堆,则之后所有的以上函数的调用都必须加上对应的参数 ,否则结果就会出错。
3.以上函数的第三个参数,我们有两种给出实参的方法:
(1)采用仿函数对象 ,这是C++的习惯,对应的头文件为: #include
例如: 小顶堆------->greater
(2)采用自定义函数名,并将该函数名传递给第三个参数,具体如下:
bool less_cmp(const int &a,const int &b)//等价于: less最后,我们给出一个小顶堆的测试程序及其输出结果:() { return abool greater_cmp(const int &a, const int &b) //等价于: greater() { return a>b; }
#include
#include
#include
#include//后面用到copy函数和heap相关函数
#include//后面用到迭代器ostream_iterator
#include//后面用到了一个比较的仿函数greater
using namespace std;
typedef vector<int> Vint;
void print(const Vint& vec)//输出当前vector容器中的元素
{
cout<<"容器内的元素为:";
copy(vec.begin(),vec.end(),ostream_iterator<int>(cout," "));//将容器内的元素输出到标准输出设备上
cout< cout<<"容器内元素的个数为:"<bool cmp(const int &a,const int &b)
{
return a>b;//大顶堆,则cmp相等于greater(),注意不是greater,前者是一个对象,后者是一个类
}
int main()
{
int arr[]={3,2,1,9,4,12,15,7};
vector<int>vec(arr,arr+sizeof(arr)/sizeof(int));//创建一个vector容器对象,将数组的副本压入到该容器中
cout<<"-----------初始状态---------------"< print(vec);//将最初的vector容器的内容输出
cout<<"-------------建堆----------------"< make_heap(vec.begin(),vec.end(),cmp);//新建一个小顶堆
//上行代码等价于make_heap(vec.begin(),vec.end(),greater()
print(vec);
cout<<"----------弹出堆顶元素-----------"< pop_heap(vec.begin(),vec.end(),cmp);//这里也要加cmp,因为弹出之后要给出向下调整的规则,否则系统会调用默认的最大堆调整方法
print(vec);
cout<<"--------向堆中插入值6的方法--------"< vec.push_back(6);//先将待插入的值放在容器的末尾
push_heap(vec.begin(),vec.end(),cmp);//再最堆进行向下调整
print(vec);
cout<<"----------执行堆排序--------------"< sort_heap(vec.begin(),vec.end(),cmp);
print(vec);
return 0;
}
测试结果如下:
操作①: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())堆排序。(没用过)