堆是一种以数组形式存放数据的数据结构,并且也具有二叉树的某些性质的数据结构,因此可以使用堆可以很有效地方问和检索堆中的数据,很方便地对其进行插入和删除操作。
本文将会分成三个部分来介绍堆,
第一部分从堆的形式化的定义和堆中所定义的堆操作的接口定义。
第二部分给出堆接口定义中的方法实现,即算法复杂度的分析,最后实现一个以堆为类名的类模板的封装。
第三部分介绍在STL中如何使用堆来进行数据的排序
第四部分给出在ACM中使用堆来解决问题的问题和解题报告
poj 2010 & poj 2823 & poj2442 &poj3481&poj2051
第一部分:
1.堆的定义
n 个元素我们称之为堆,当且仅当它的关键字序列 k1 , k2 , k3 ..... , kn 满足:
; ( 1.1 )
或者满足:
; ; (1.2)
我们把满足式子 1.1 的堆称之为最小堆, 而把满足式子 1.2 的堆称之为最大堆, 对于堆来说,可以从它的定义看出来,实质上它是一个完全二叉树。
如果树的高度为 d , 并约定根的层次为 0 , 则堆是具有如下性质的:
1,所有的叶节点不是出于第 d 层,就是出于 d-1 层。。
2,当 d >= 1 的时候, 在第 d-1 层上有 2^(d-1) 个节点 。
3, 第 d-1 层上如果有分支节点,则这些分支节点都集中在树的最左边。
4. 每个节点所存放元素的关键字,都大于(最大堆) 或 小于 (最小堆)其子孙节点所存放的关键字。
对于一个具有n个元素的堆,可以很方便地用如下的方法,有数组H来存取它。
1. 根节点通常存放在 H[1] 中。
2. 假定结点 x 存放在 H[i] , 如果它有左儿子,则左儿子节点存放在 H[2i] 中, 如果有右儿子节点,则右儿子节点存放在 H[2i+1] 中
3. 非根节点 H[i] 的父节点存放在 中。
堆操作的接口定义:
void sift_up ( Type H [] , int i ) ;
//作用是将堆中第 i 个元素上移
void sift_down ( Type H [] , int n , int i );
//作用是将堆中的第 i 个元素下移
/**
在这里是这样的, 对于元素在堆中上移的时候,是从底到上进行不断比较,各个节点上面的元素的大小的,所以到根 H[1] 这个过程就会停止,
但是在元素堆中下移的时候,是需要通过堆中元素的大小从当前位置到 堆中最后一个元素进行比对的, 如果不指定最后一个元素的位置,或是堆中元素的多少的话,会造成越界访问的问题。
*/
void insert ( Type H [] , int &n , Type x ) ;
//该操作实现的是将元素 x 插入到堆中
Type delete ( Type H [] , int &n , int i ) ;
//该操作实现的是将元素 x 从堆中删除,并且将删除的元素作为
//返回值进行返回
void make_head ( Type H [] , int n ) ;
// 使数组 H 中的元素按照堆结构从新的组织
第二部分:
1. 元素上移操作
假定所使用的堆是最大堆。当修改了堆中某一个节点的关键字的数值的时候,使得这个关键字的数值大于它父亲的关键字,这就会打破最大堆中父节点大于两个子节点中的元素值的平衡,即违反了最大堆的性质。为了重新恢复最大堆的性质,需要把该元素上移到合适的位置。这个时候我们可以通过 sift_up 这个方法来使得整个堆再次恢复到平衡的状态。
其中,sift_up 操作会沿着 H[i] 到根 H[i] 的一条路线,把 H[i] 向上移动。 在移动的过程中, 把它和它的父节点进行比较,即比较 H[i] 和 H [i/2] 的大小,如果
H[i] > H[i/2] 的话, 则将置换 H[i] <-> H[i/2] 二者的位置。如此进行下去,直至H[i] 找到一个合适的位置为止。
/*
算法 1 元素的上移操作
输入: 数组 H [] 及被上移的元素在数组 H 中的下标 i
输出: 维持堆的性质的数组 H []
*/
template <class Type >
void sift_up ( Type H [] , int i )
{
bool done = false ;
if ( i != 1 )
{
while ( !done && i != 1 )
{
if ( H [i] > H[i/2] )
swap (H[i] , H[i/2]) ;
else
done = true ;
i = i/2 ;
}
}
}
template < class Type >
void swap ( Type &a , Type &b )
{
Type h = a ;
a = b ;
b = h ;
}
算法中的 i/2 操作是整出操作。在后面的算法中, 若是 a, b 是整数的话,则操作 a/b 均是表示的是整除操作。元素每进行一次移动,就会执行一次比较
操作。如果移动成功的话,他所在节点的层数就会-1. n 个元素一共有 (logn) 下取整,层的节点,所以sift_up 对应执行最多次数的操作是,i 的值是位于
堆的最下层,但是其 H[i] = Max ( H [k] ) 1<=k<=n