堆是一个用途很广泛的数据结构,是实现topK问题、堆排序以及优先级队列等问题的必备工具。深刻理解堆这种数据结构,掌握实现堆的技能是学习数据结构非常重要和必备的
一环。
堆的本质是:
vector + 向上调整 和 向下调整
人们在想象中把它抽象为一棵每个父亲节点都比它两个子节点 大/小 的二叉树
并用二叉树的下标表示方法构建和调整这棵树。实际上所有的结点都在vector中
顺序存储。
向下调整:
(_v.size() - 2) >> 2 找到最后一个父亲结点 若其比儿子中的某一个小 则将两个孩子中较大/小的结点与其父亲结点互换 之后 parent = child ; child= parent*2 +1;(向下继续处理)Pop() 和 构造堆要用到。
向上调整:
Push()时要用到 这时原树已经是堆了 在其最后加一个结点然后 查看新加结点是否比父亲大/小 如果是交换它和其父亲 之后 child = parent; parent= (child - 1)>>1.
以下是实现一个堆的代码:
“堆.cpp”
#pragma once
#include
#include
#include
using namespace std;
//利用仿函数代替比较运算符
//Compare()(_v[child], _v[]parent) 其实是Compare( ) 创建一个临时对象 调用其operator()
template < class T >
struct Greater
{
bool operator ( )( const T & l, const T & r)
{
return l > r;
}
};
template < class T >
struct Less
{
bool operator ( )( const T & l, const T & r)
{
return l < r;
}
};
template < class T , class Compare = Greater < T >>
class Heap
{
public :
Heap ()
{}
bool Empty ()
{
return _v. empty ();
}
size_t Size ()
{
return _v. size ();
}
Heap ( const T * arr, int n)
{
_v. reserve (n); //为了提高效率 一次性申请够空间 防止插入时空间不够再次申请。
for ( int i = 0; i < n; ++i)
{
_v.push_back(arr[i]); //一般和reserve()函数配合使用
}
for ( int i = ( int )(_v. size () - 2) / 2; i >= 0; --i)
{
AdjustDown (i); //请注意向下调整是被循环调用的 最早从树的最后一个父亲结点开始
//目的是为了将原本vector中无规律的数变为符合堆的规律。
//每一次调用完AdjustDown(i)只保证 i下子树是堆
}
}
void Pop () //移除堆顶方法
{
swap (_v[_v. size () - 1], _v[0]); //先将堆顶结点和堆最后结点互换位置
_v. pop_back (); //之后移除原本是对顶的最后一个结点
//之所以这么做是为了让原对顶的左右子树保持堆的性质。
AdjustDown (0); //把原来是最后结点的现堆顶搞一次向下调整。
}
void Push ( const T & data) //新插入结点
{
_v.push_back(data); //先将新结点权且放在最后以保证堆内其他结点符合堆的性质
AdjustUp (_v. size () - 1); //注意调用向上调整时只调一次 向上调整是为了Push而存在的
}
const T & Top ()
{
return _v[0];
}
void Printheap ()
{
for ( size_t i = 0; i < _v. size (); ++i){
cout << _v[i] << " " ;
}
cout << endl ;
}
private :
void AdjustDown ( int root) //向下调整
{
int parent = root; //已有最后一个父亲结点
int child = parent * 2 + 1; //默认找到其左孩子
while (child < _v. size ()){
if (child + 1 < _v. size () && Compare ()(_v[child + 1], _v[child]))
{
child++; //这里是为了找到两个孩子里较大的准备和父亲换
}
else if ( Compare ()(_v[child], _v[parent])){
swap (_v[child], _v[parent]); //如果大孩子比父亲大 则换
parent = child;
child = parent * 2 + 1; //向下继续
}
else {
break ; //大孩子比父亲小 次树是堆出循环
}
}
}
void AdjustUp ( int index) //向上调整
{ //注意此时除index外其余结点符合堆的性质
int child = index;
int parent = (child - 1) / 2;
while (child > 0){
if ( Compare ()(_v[child], _v[parent])){
swap (_v[child], _v[parent]); //比父亲大和父亲换
child = parent;
parent = (child - 1) / 2; //向上调整
}
else {
break ; //比父亲小了 出循环
}
}
}
vector < int > _v;
};
topk问题(也是海量数据处理问题):
问题:需要从十亿个数据中找出最大的前k个数。
分析思路:
思路:不能使用排序,因为使用排序的话,内存就必须可以容纳十亿个数据,但是这很明显不可能,所以不能使用排序。
我们可以先从十亿个数据中取出前k(假如为100)个数据,使用这k个数据来建一个小堆,那么堆顶就是这个堆中最小的数据,
然后我们就从十亿减k个数据中取出一个数据赖和堆顶数据来进行比较,如果比堆大,那么就拿这个数据来替换堆顶数据。
然后采用向下调整算法,使之堆顶又是这个堆中最小的数据。依次比较。。替换。。。调整。。。
最终,堆中的前k个数据就是十亿个数据中最大的k个数据。
这里应该可以想到:最大的前k个数据一定会进堆。–>因为每次都是比较堆顶的数据和从剩余的数据进行比较,
而这个堆顶数据又是这个堆中最小的数据。
注意:不能建大堆,如果建大堆,然后从剩余的十亿数据中取数据,拿取到的数据和堆顶数据进行比较,
如果这个数据大于堆顶的数据,那么就交换两者的数据。最终只能找出十亿数据中最大的一个数据。—->不符合。
以下是关于对的优先级队列的使用 及topK问题的解决:
#include "堆.cpp"
#define N 1000
#define K 20
//优先级队列为堆的简单封装
template < class T >
class PriorityQueue
{
public :
PriorityQueue ()
{}
void Push ( const T & data)
{
hp. Push (data);
}
void Pop ()
{
hp. Pop ();
}
const T & Top ()
{
return hp. Top ();
}
size_t Size ()
{
return hp. Size ();
}
bool Empty ()
{
return hp. Empty ();
}
private :
Heap < int , Greater < int >> hp;
};
void Test ()
{
int array[] = { 53, 17, 78, 9, 45, 65, 87, 23 };
Heap < int , Greater < int > > h(array, sizeof (array) / sizeof ( int ));
h. Push (80);
h. Pop ();
}
void TestPriorityQueue ()//测试优先级队列
{
int array[] = { 53, 17, 78, 9, 45, 65, 87, 23 };
size_t len = sizeof (array) / sizeof ( int );
PriorityQueue < int > p;
for ( size_t index = 0; index < len; ++index)
{
p. Push (array[index]);
}
cout << p. Top () << endl ;
p. Pop ();
cout << p. Top () << endl ;
}
//TopK问题
void TopK ()
{
int arr[ N ];
int brr[ K ];
for ( size_t i = 0; i < N ; ++i){
arr[i] = rand () % N ;
}
for ( size_t i = 0; i < K ; ++i){
brr[i] = arr[i];
}
Heap < int , Less < int >> hp(brr, sizeof (brr) / sizeof ( int ));
for ( size_t i = K; i <= N ; ++i){
if (arr[i] > hp. Top ()){
hp. Pop ();
hp. Push (arr[i]);
}
}
hp. Printheap ();
}
int main ()
{
/*Test();
TestQueue();*/
TopK ();
system ( "pause" );
return 0;
}