binary heap就是一种完全二叉树。也就是整颗树除了最底层的叶节点之外
heap的所有元素遵循完全二叉树的排序规则,不提供遍历功能,也不提供迭代器。
array的缺点是无法动态改变大小,而heap却需要,可用vector代替array
heap分为max-heap和min-heap
STL提供的是max-heap,对max-heap进行分析。
为了满足完全二叉树的条件,新加入的元素一定要放在最下一层作为叶节点,并填补在由左至右的第一个空格,也就是插入在vector的end()处。然后为了满足max-heap的条件,也就是每个节点大于等于其子节点的键值,执行一个上溯(percolate up)程序:将新节点与父节点比较,若比父节点大,就对换位置,如此一致上溯,知道不需对换或者根节点为止。
template <class RandomAccessIterator>
inline void push_heap(RandomAccessIterator first, RandomAccessIterator last) {
// 注意,调用该函数时候,新元素位于最后一个位置(last-1)。
__push_heap_aux(first, last, distance_type(first), value_type(first));
}
template <class RandomAccessIterator, class Distance, class T>
inline void __push_heap_aux(RandomAccessIterator first,
RandomAccessIterator last, Distance*, T*) {
__push_heap(first, Distance((last - first) - 1), Distance(0),
T(*(last - 1)));
// (last-first)–1代表新元素的索引,0是堆首的索引,*(last - 1)是新加入的值
}
template <class RandomAccessIterator, class Distance, class T>
void __push_heap(RandomAccessIterator first, Distance holeIndex,
Distance topIndex, T value) {
Distance parent = (holeIndex - 1) / 2; // 找出父节点
while (holeIndex > topIndex && *(first + parent) < value) {
// 尚未到达顶端,且父节点小于新值
// 由于以上使用 operator<,可知 STL heap 是max-heap
*(first + holeIndex) = *(first + parent); // 令洞值为父值
holeIndex = parent; // percolate up:调整洞号,向上提升至父节点。
parent = (holeIndex - 1) / 2; // 新洞的父节点
} // 持续至顶端,或满足 heap 的次序特性为止。
*(first + holeIndex) = value; // 令洞值为新值。
}
max-heap最大值在根节点,pop操作取走根节点(vector的尾端节点)后,为了满足complete binary tree的条件后,必须割舍最下层右边的叶节点,并将其值重新安插至max-heap。
执行下溯程序:将空间节点与其较大子节点对调,并持续下放直到叶节点为止,然后将割舍的元素值设给这个已到达叶层的空洞节点,再执行一次上溯程序,即可。
pop_heap会把键值最大的元素放在底部容器的最尾端。
inline void pop_heap(RandomAccessIterator first, RandomAccessIterator last) {
__pop_heap_aux(first, last, value_type(first));
}
template <class RandomAccessIterator, class T>
inline void __pop_heap_aux(RandomAccessIterator first,
RandomAccessIterator last, T*) {
__pop_heap(first, last - 1, last - 1, T(*(last - 1)), distance_type(first));
// pop动作的結果为底层容器的第一個元素。因此,首先设定欲调整值为尾值,然后將首值調至
// 尾节点(所以以上將迭代器result设为last-1)。然后重整 [first, last-1),
// 使之重新成一個合格的 heap。
}
template <class RandomAccessIterator, class T, class Distance>
inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last,
RandomAccessIterator result, T value, Distance*) {
*result = *first; // 設定尾值为首值,于是尾值即是結果,
// 可由调用底层容器之 pop_back() 取出尾值。
__adjust_heap(first, Distance(0), Distance(last - first), value);
// 以上欲重新調整 heap,洞号为 0,欲調整值为value。
}
template <class RandomAccessIterator, class Distance, class T>
void __adjust_heap(RandomAccessIterator first, Distance holeIndex,
Distance len, T value) {
Distance topIndex = holeIndex;
Distance secondChild = 2 * holeIndex + 2; // 洞节点之右子节点
while (secondChild < len) {
// 比较洞节点之左右兩个子值,然后以 secondChild 代表较大子节点。
if (*(first + secondChild) < *(first + (secondChild - 1)))
secondChild--;
// Percolate down:令较大大子值为洞值,再令洞号下移至较大子节点处。
*(first + holeIndex) = *(first + secondChild);
holeIndex = secondChild;
// 找出新洞节点的右子节点
secondChild = 2 * (secondChild + 1);
}
if (secondChild == len) { // 沒有右子节点,只有左子节点
// Percolate down:令左子值为洞值,再令洞号下移至左子节点处。
*(first + holeIndex) = *(first + (secondChild - 1));
holeIndex = secondChild - 1;
}
// 將欲调整值填入目前的洞号內。注意,此時肯定滿足次序特性。
// 依侯捷之见,下面直接改為 *(first + holeIndex) = value; 应该可以。
__push_heap(first, holeIndex, topIndex, value);
}
sort_heap不断地对heap进行pop_heap,使heap变成一个递增序列。
// 以下这个sort_heap() 不允许指定“大小比较标准”
template <class RandomAccessIterator>
void sort_heap(RandomAccessIterator first,RandomAccessIterator last)
{
// 以下,每执行一次 pop_heap(),极值(在STL heap中为极大值)即被放在尾端
// 扣除尾端再执行一次 pop_heap(),次极值又被放在新尾端。一直下去,最后即得排序结果
while (last - first > 1)
pop_heap(first, last--); // 每执行 pop_heap() 一次,操作范围即退缩一格
}
将一段数据转为一个heap,其主要依据就是文章一开始提到的complete binary tree的隐式表述(implicit representation)
// 将 [first,last) 排列为一个heap
template <class RandomAccessIterator>
inline void make_heap(RandomAccessIterator first,RandomAccessIterator last)
{
__make_heap(first, last, value_type(first), distance_type(first));
}
// 以下这组 make_heap() 不允许指定“大小比较标准”
template <class RandomAccessIterator, class T, class Distance>
void __make_heap(RandomAccessIterator first,RandomAccessIterator last, T*,Distance*)
{
if (last - first < 2)
return; //如果长度为0或1,不必重新排列
Distance len = last - first;
// 找出第一个需要重排的子树头部,以parent标示出。由于任何叶节点都不需执行
// perlocate down,所以有以下计算。parent命名不佳,名为holeIndex 更好
Distance parent = (len - 2)/2;
while (true) {
// 重排以 parent 为首的子树。len是为了让__adjust_heap() 判断操作范围
__adjust_heap(first, parent, len, T(*(first + parent)));
if (parent == 0)
return; // 走完根节点,就结束。
parent--; // (即将重排之子树的)头部向前一个节点
}
}
make_heap(_First, _Last)//默认建大根堆,其中_First, _Last为指针
make_heap(_First,_Last,greater<int>())//建小根堆
make_heap(_First,_Last,less<int>())//建大根堆
push_heap (_First, _Last)//要现在vector中添加数据,其实相当于重新排序成大根堆
push_heap (_First, _Last,greater<int >())//注意比较函数一定要与make_heap建堆时一致
push_heap (_First, _Last,less<int >())//在大根堆中加入元素
pop_heap(_First, _Last)//弹出堆顶元素,实际是放到容器的最后,配合vector.pop_back()使用
pop_heap(_First, _Last,greater<int >())//同样要注意大小堆的问题
pop_heap(_First, _Last,greater<int >())
sort_heap(_First, _Last,greater<int >()) //堆排序,小根堆只能降序
sort_heap(_First, _Last,less<int >())//大根堆只能升序
#include
#include
#include
using namespace std;
int main()
{
int arr[9] = { 0,1,2,3,4,8,9,3,5 };
vector<int> ivec(arr, arr + 9);
//构造一个最大堆
make_heap(ivec.begin(), ivec.end());
for (int i = 0; i < ivec.size(); ++i)
cout << ivec[i] << " "; //9 5 8 3 4 0 2 3 1
cout << std::endl;
ivec.push_back(7); //将7添加到尾部
for (int i = 0; i < ivec.size(); ++i)
cout << ivec[i] << " "; //9 5 8 3 4 0 2 3 1 7
cout << std::endl;
/*
上面将7添加到尾部,破坏了完全二叉树的结构,因此需要调用此函数
显式说明尾部有个元素需要插入到完全二叉树中,因此下面打印的为重新调整后的元素顺序
*/
push_heap(ivec.begin(), ivec.end());
for (int i = 0; i < ivec.size(); ++i)
cout << ivec[i] << " "; //9 7 8 3 5 0 2 3 1 4
cout << std::endl;
pop_heap(ivec.begin(), ivec.end());//只是将根节点移到容器尾部,但是还没有删除
cout << ivec.back() << std::endl; //打印9
ivec.pop_back(); //将最后的尾节点9移除
for (int i = 0; i < ivec.size(); ++i)
cout << ivec[i] << " "; //8 7 4 3 5 0 2 3 1
cout << std::endl;
sort_heap(ivec.begin(), ivec.end()); //对vector进行堆排序
for (int i = 0; i < ivec.size(); ++i)
cout << ivec[i] << " "; //0 1 2 3 3 4 5 7 8
cout << std::endl;
return 0;
}