您的位置:首页 > 编程语言 > C语言/C++

heap最大(小)堆

2016-08-27 00:03 274 查看
首先是概念,堆属于完全二叉树。

完全二叉树百度百科描述是:

(1)若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数。

(2)第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

最大堆对于完全二叉树的节点数值要求是:

根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。也就是说左右子树也是一个大顶堆。

堆一般通过数组表示。从上到下从左到右一个个排列。

先给个我们使用heap的例子

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
vector<int> v;
v.reserve(16);
v.push_back(1);
v.push_back(5);
v.push_back(8);
v.push_back(6);
v.push_back(7);
make_heap(v.begin(),v.end());       //先创建一个大顶堆再说
for(int i = 0; i < v.size(); ++i)
{
cout << v[i] << " ";
}
cout << endl;                       //输出结果 8 7 1 6 5 最大的8到最前面去了吧~

pop_heap(v.begin(),v.end());        //吧最大那个就是第一个弄出来,vector里的值会删除吗??不会
for(int i = 0; i < v.size(); ++i)
{
cout << v[i] << " ";
}
cout << endl;                      //输出结果 7 6 1 5 8   看到了吧,把8放到最后去啦~~前面的依旧重新自己调整为大顶堆
pop_heap(v.begin(),v.end()-1);     //把7也取出来吧 如果再也不想看到8,你就把vector最后一个erase掉
for(int i = 0; i < v.size(); ++i)
{
cout << v[i] << " ";
}
cout << endl;                     //输出结果 6 5 1 7 8 
v.push_back(1000);
 push_heap(v.begin(),v.end());    //输出结果 1000 5 6 7 8 1   (先往vector尾端插入再push_heap)
}
记住两点

(1)pop_heap发生在开始位置(它最大嘛),push_heap在最后。

(2)pop和push前都得保证它是一个heap啊!都不是一个heap了操作它也没意义了啊

一直pop_heap会怎么样啊?对,就是heap从小到大排序。下面是STL sort_heap实现代码:

sort_heap(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp)
{
__STL_REQUIRES(_RandomAccessIterator, _Mutable_RandomAccessIterator);
while (__last - __first > 1)
pop_heap(__first, __last--, __comp);
}


那么如何创建出一个大顶堆。(需要小顶堆可传入compare比较函数)

template <class _RandomAccessIterator, class _Tp, class _Distance>
void
__make_heap(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Tp*, _Distance*)
{
if (__last - __first < 2) return;         //没有或者只有1个元素不用排序了
_Distance __len = __last - __first;       //有多少个元素保存起来,作越界判断
_Distance __parent = (__len - 2)/2;      //从后往前开始,取出最后一个parent
while (true) {
//把这个value放到一个合适的位置。先放着略过,等下分析这个
__adjust_heap(__first, __parent, __len, _Tp(*(__first + __parent)));
if (__parent == 0) return;
__parent--; //一个个节点来吧,感觉好费啊
}
}



最最重要的函数__adjust_heap,功能是把当前节点子树调整为一个大顶堆树。

它主要的思想是这样的:

把要调整的节点的值先取出来,就是这个参数value,然后把这个节点当做空节点,把max(left_child,right_child)大的孩子值放入这个位置,然后位置下移一层(二叉树的度),赋值的那个孩子再次当做parent,他的值已经给上一层了,所以他变成空节点了,他也得从孩子节点选大的来填充,如此循环下去到最大深度的叶子节点,child就都小于等于parent啦~~

还有一个最开始的value呢?最后把这个value放到最下层的那个空节点,然后让他一点一点往上和parent比较往上移动吧

template <class _RandomAccessIterator, class _Distance, class _Tp>
void
__adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex,
_Distance __len, _Tp __value)
{
_Distance __topIndex = __holeIndex;                           //把这个index当做根节点(子树)
_Distance __secondChild = 2 * __holeIndex + 2;                //把right_child取出来
while (__secondChild < __len) {
if (*(__first + __secondChild) < *(__first + (__secondChild - 1))) //left_child和right_child选出大的
__secondChild--;
*(__first + __holeIndex) = *(__first + __secondChild);     //胜者才有资格放入parent
__holeIndex = __secondChild;
__secondChild = 2 * (__secondChild + 1);                  //继续往下遍历right_child吧
}
//_seondChild是0开始的下标,最后一个叶子节点的处理,如果只有left_child才会出现等于
if (__secondChild == __len)
{
*(__first + __holeIndex) = *(__first + (__secondChild - 1)); //没right_child和你争了
__holeIndex = __secondChild - 1;
}
__push_heap(__first, __holeIndex, __topIndex, __value); //一层一层往上爬。
//(注侯捷老师的注解 可换成*(first + holeIndex) = value,有误,不能换。make_heap没问题但是pop_heap有问题)
}


</pre><pre>
看看怎么爬的吧。。

__push_heap(_RandomAccessIterator __first,
_Distance __holeIndex, _Distance __topIndex, _Tp __value)
{
_Distance __parent = (__holeIndex - 1) / 2;
while (__holeIndex > __topIndex && *(__first + __parent) < __value) {
*(__first + __holeIndex) = *(__first + __parent);          //战胜parent把他换下来~~
__holeIndex = __parent;
__parent = (__holeIndex - 1) / 2;
}
*(__first + __holeIndex) = __value;                        //最后终于找到合适的位置了
}


pop_heap也贴一下

template <class _RandomAccessIterator, class _Tp>
inline void
__pop_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last,
_Tp*)
{
__pop_heap(__first, __last - 1, __last - 1,
_Tp(*(__last - 1)), __DISTANCE_TYPE(__first)); //*(last-1)把最后那个值取出来

}

template <class _RandomAccessIterator, class _Tp, class _Distance>
inline void
__pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
_RandomAccessIterator __result, _Tp __value, _Distance*)
{
//把最大值放到最后去,最后那个值是已经取出来过的(__value)~不怕
*__result = *__first;
//都去抢那个空位置去吧,该函数最后一行处理一下这个可能真的是很大的value哦
__adjust_heap(__first, _Distance(0), _Distance(__last - __first), __value);
}



个人总结:挖空节点一个再处理,把它保存起来,调动其他节点的处理。make_heap感觉有点费,make_heap while遍历处理节点比较过多。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++ STL heap