SGI-STL源码剖析之Heap

简介: SGI-STL源码剖析之Heap

binary heap 是一种 Complete binary tree, 也就是,整棵 binary tree 除叶子节点之外,是填满的,而最底层的叶节点由左至右又不得有空隙。

heap 的实现

假设动用 一 个小技巧 3 ,将 array 的 #0 元素保留(或设为无限大值或无限小值),那么当 complete binary tree 中 的某个节点位 于 array 的i 处,其左子节点必位于 array 的2i 处,其右子节点必位于 array 的2i+1 处,其父节点必位于「i/2 」处 (此处的「」权且代表高斯符号,取其整数)。通过这么简单的位置规则,array 可以轻易实作出 complete binary tree 。这种以array 表述 tree 的方式,我们称为隐式表述法(implicit representation )。

注意: STL 中并未使用此技巧,此点需要特别注意,后面的所有的下标都与此有关。

heap 的分类

根据元素排列方式, heap 可分为 max-heap 和min-heap 两种,前者每个节点的键值( key )都大于或等于其子节点键值,后者的每个节点键值( key )都小于或等于其子节点键值。因此, max-heap 的最大值在根节点,并总是位于底层 array 或 vector 的 起 头 处 ; min-heap 的 最 小 值 在 根 节 点 , 亦 总 是 位 于 底 层 array 或 vector 的起头处。

注意: STL 提供的是 max-heap

heap 算法依赖关系

push_heap 算法

为了满足 complete binary tree 的条件,新加入的元素 ㆒ 定要放在最 下一 层做为叶节点,并填补在由左至右的第 一 个空格,也就是把新元素安插在底层 vector 的 end() 处。新元素是否适合于其现有位置呢?为满足 max-heap 的条件(每个节点的键值都大于或等于其子节点键值),我们执行 一 个所谓的 percolate up ( 上 溯)程序:将新节点拿来与其父节点比较,如果其键值( key )比父节点大,就父子对换位置。如此 一 直 上 溯,直到不需对换或直到根节点为止。

//first: 底层容器的起始位置 iterator
//holeIndex: 新值将被置于的位置
//topIndex: 根节点在容器中的索引
//value: 新值的值
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) {
        // 当尚未到达顶端,且父节点小于新值(于是不符合 heap 的次序特性)
        // 由于以 上 使用 operator< ,可知 STL heap 是 ㆒ 种 max-heap (大者为父)。
        *(first + holeIndex) = *(first + parent); // 令洞值为父值
        holeIndex = parent; // percolate up :调整洞号,向 上 提升至父节点。
        parent = (holeIndex - 1) / 2; // 新洞的父节点
    } // 持续至顶端,或满足 heap 的次序特性为止。
    *(first + holeIndex) = value; // 令洞值为新值,完成安插动作。
}

pop_heap 算法

pop 动作取走根节点(其实是移至底部容器 vector 的最后一个元素)之后,为了满足 complete binary tree 的条件,必须割舍最下层最右边的叶节点,并将其值安插至 max_heap (安插时用到 __push_heap )。(取走根节点后)为满足 max-heap 的条件(每个节点的键值都大于或等于其子节点键值),我们执行一个所谓的 percolate down (下溯)程序:将根节点取走(最大值被取走后,形成一个「 hole 」),然后比较其两个子节点键值( key ),并与较大子节点对调位置。如此一直下放,直到这个「 hole 」的键值大于左右两个子节点,或直到下放至叶节点为止。

//first: 底层容器的起始位置 iterator
//holeIndex: 被取走的根植形成 hole 位置的索引
//len: 指定了排序的区域大小
//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; // 洞节点之右子节点
    // 注意以下的比较,仅仅是左右子节点的比较,并且以两者较大者填充 hole ,然后查找新的 hole ,直至将 hole 下放到 // 叶节点。如果在此过程中割舍点的值同时大于左右两个节点,则不满足 max_heap ,这是最后需要执行一次 //__push_heap 的原因所在,所以 __push_heap 必不可少
    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;
    }
    __push_heap (first, holeIndex, topIndex, value);
}

sort_heap 算法

既然每次 pop_heap 可获得 heap 之 中 键值最大的元素,如果持续对整个 heap 做pop_heap 动作,每次将操作范围从后向前缩减 一 个元素(因为 pop_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() 一 次,操作范围即退缩 一 格。
}

make_heap 算法

这个算法用来将一段现有的数据转化为一个heap 。其主要依据就是 complete binary tree 的隐式表述( implicit representation )。

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 的作用:每次 while 循环 确保且仅仅确保 了 first 到 parent 之间的数据满足 heap 。
        //__adjust_heap 下溯的 while 循环确保了其下溯路线上的节点都大于和其共父节点的节点, //__adjust_heap 上溯的 __push_heap 确保了上述下溯路线上节点父节点大于子节点
        // 综上所诉, __adjust_heap 确保了且仅仅确保了 first 到 parent 之间的数据满足 heap 。
        __adjust_heap (first, parent, len, T(*(first + parent)));
        if (parent == 0) return;
        parent--;
        // 走完根节点,就结束。
        // (即将重排之子树的)头部向前一个节点
    }
}

本文作者 : cyningsun

本文地址https://www.cyningsun.com/04-04-2011/stl-heap.html

版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!

# STL

  1. STL算法我实现之随机洗牌
  2. STL算法我实现之排列
  3. STL算法我实现之rotate
  4. SGI-STL源码剖析之IntroSort
  5. SGI-STL源码剖析之list::sort()
目录
相关文章
|
Java API 数据处理
使用Java内存映射(Memory-Mapped Files)处理大文件
NIO中的内存映射 (1)什么是内存映射文件 内存映射文件,是由一个文件到一块内存的映射,可以理解为将一个文件映射到进程地址,然后可以通过操作内存来访问文件数据。说白了就是使用虚拟内存将磁盘的文件数据加载到虚拟内存的内存页,然后就可以直接操作内存页数据。
7167 0
|
7月前
|
缓存 Java
直接内存(Direct Memory)牛刀小试
直接内存(Direct Memory)牛刀小试
42 0
|
存储 算法 Java
JEP 331: Low-Overhead Heap Profiling
JEP 331: Low-Overhead Heap Profiling
111 1
|
Docker 容器
解决Native memory allocation (mmap) failed to map 2060255232 bytes for committing reserved memory.
解决Native memory allocation (mmap) failed to map 2060255232 bytes for committing reserved memory.
1338 0
vxworks block too big 问题的解决
vxworks block too big 问题的解决
149 0
vxworks block too big 问题的解决
|
小程序
!heap 和 _HEAP_ENTRY
WinDBG提供了!heap命令帮助我们查找heap,同时我们也可以通过dt和MS SYMBOL来了解memory layout。   假设我们有下面一个小程序。   int _tmain(int argc, _TCHAR* argv[]){ char * pChar = new char[2]; pChar[0] = 'a';  delete [] pChar; return 0;}   在WinDbg里面启动这个程序,运行到new操作结束。
796 0
|
Java Android开发 图形学
浅谈 maxMemory , totalMemory , freeMemory 和 OOM 与 native Heap
作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguanh/ GitHub : https://github.com/af913337456/ 腾讯云专栏: https://cloud.tencent.com/developer/user/1148436/activities 回答内存管理类面试问题可以说出下面这些内容,加分。
1729 0