堆 是一颗被完全填满的二叉树 堆分大堆小堆 性质:http://baike.baidu.com/link?url=7fnAEA2vNRAskkWEWae4gmg3mfzAWAtxueXn2-gKX05N-jvbOqiuu7urw1K70bQAh876IL6XpT4jGBrpPMUX-xWHwWG53hzFLij6XxQaQuS#4
下面的代码展示了通过一个数组如何构建一个堆,以及堆的pop与push操作 (此处为大堆,小堆类似)
调整堆:首先我们要明确调整堆的目的就是让整棵树中的双亲节点都大于孩子节点(这里以最大堆为例),所以我们要从叶子结点开始调整,直到调整到根节点结束,可能调整好这棵树后,子树又不符合最大堆规则,转而调整子树,所以我们把这种方式叫下调(AdjustDown)
我们通过向下调整算法来构建一个堆 从 最后一个非叶子节点开始调整(最后一个结点的父亲)依次向上,调整结束后完成一个堆。
Push 和 Pop操作通过两种方式来是实现
1.push与pop操作利用了 hole为空洞 进行调整 的想法
2.利用向下和向上调整算法 插入时只调整插入位置到根节点位置 删除时向下调整使之重新成为一个堆
.h:
//数组坐标
//左孩子坐标 * 2 + 1
//右孩子坐标* 2 + 2
//找父亲-1 / 2
//完全二叉树:底层的元素从左到右填充
//resize 开空间还赋值(先初始化为默认值)
//reserve 只开空间
//最后一个结点父亲 是 最后一个非叶子节点 从它开始调整
//注意 如果 某个值在小于0 时退出循环 不要用 size_t 否则会死循环
//此处构建的是大堆
#include <vector>
template<class T>
class Heap
{
public:
Heap( )
{}
Heap( T* a, size_t n )
{
_a.reserve( n );//resize 开空间还赋值 reserve 只开空间
for ( size_t i = 0; i < n; ++i )
{
_a.push_back( a[i] );
}
AdjustDown( );
}
void AdjustDown( )//成员函数嘛,这里参数也不好写, 不如在里面调用子函数(给它传参),在外面通过对象调用这个函数就好,让它调用子函数 (but 外界对象不能直接调用保护的子Fun)
{
for ( int i = (_a.size( ) - 2) / 2; i >= 0; --i )//必须用int 否则死循环. 条件 >= 0;
{
_AdjustDown( i );
}
}//这两个函数忽略,用书上的方法
void AdjustUp( )
{
_AdjustUp( _a.size( ) - 1 );
}
//void Push( const T& x )//1
//{
//_a.push_back( x );
//T tmp = x;
//int hole = _a.size( ) - 1;
//for ( ; (hole > 0) && (tmp > _a[(hole - 1) / 2]); (hole = (hole - 1) / 2) )//条件不能是>=0. 如果等于0则出错!
//{
//_a[hole] = _a[(hole - 1) / 2];
//}
//_a[hole] = tmp;
//}
void Push( const T& x )//插入元素x后, 只影响插入元素到根节点处再到第一个元素的路径上的元素, 所以利用向上调整算法重新建堆
{
_a.push_back( x );
AdjustUp( );
}
const T& Top( )//返回的值 ,不可改变
{
return _a[0];
}
//void Pop( )
//{
//swap( _a[0], _a[_a.size( ) - 1] );
//_a.pop_back( );
//int child;
//int hole;
//T tmp = _a[0];
//for ( hole = 0; (hole * 2 + 1<= _a.size( ) - 1); hole = child )//默认为左孩子
//{
//child = hole * 2 + 1;
//if ( (_a.size( ) != child + 1) && (_a[child + 1] > _a[child]) )//右孩子存在
//{
//++child;
//}//此时 child 为左右(如果右孩子存在)孩子中最大值下标
//if ( _a[child] > tmp )
//{
//_a[hole] = _a[child];
//}
//else
//{
//break;
//}
//}
//_a[hole] = tmp;
//}
void Pop( )
{
swap( _a[0], _a[_a.size( ) - 1] );
_a.pop_back( );
_AdjustDown( 0 );//通过向下调整算法重新构建堆
}
void Print( )
{
for ( size_t i = 0; i < _a.size( ); ++i )
{
cout << _a[i] << " ";
}
}
protected:
vector<T> _a;
void _AdjustDown( size_t parent )//不合适
{
size_t child;
child = parent * 2 + 1;//此处为左孩子
while ( child < _a.size( ) )//循环内就继续调整,因为一次调整后,以前已经调整好的堆可能被破坏了
{
if ( (child + 1 < _a.size( )) && (_a[child + 1] > _a[child]) )//右孩子存在且右孩子大于左孩子值
{
++child;
}
if ( _a[child] > _a[parent] )//条件不满足则已经为最大/小堆,不需要调整
{
swap( _a[child], _a[parent] );
parent = child;
child = parent * 2 + 1;//默认从左孩子开始
}
else
{
break;
}
}
}
void _AdjustUp( size_t child )
{
size_t parent = (child - 1) / 2;
while ( child > 0 )
{
if ( _a[child] > _a[parent] )
{
swap( _a[child], _a[parent] );
child = parent;//此处 最后一个根节点 0 赋值给child 结束 所以child > 0时循环继续
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
};
.cpp: