准确来说,heap并不属于STL容器,但它是其中一个容器priority queue必不可少的一部分。顾名思义,priority queue就是优先级队列,容许用户以任何次序将任何元素加入容器内,但取出时是从优先权最高的元素开始取。而优先权有两种,能够是容器内数值最低的,也能够是数值最高的。而priority queue是选择了数值高做为评判优先级的标准。对应实现方法就是binary max heap,其做为priority queue的底层机制。算法
所谓的binary heap(二叉堆),是一种彻底二叉树,也就是说,整棵二叉树除了最底层的叶子节点外,其余节点都是被填满的,而最底层的节点是从左至右不得有空节点的,如图所示:数组
如图所示,彻底二叉树整棵树内没有任何节点漏洞,这使得咱们可使用数组来存储一棵彻底二叉树上的全部结点。另外,若是咱们数组的索引1开始记录节点,那么父节点与子节点在数组中的关系就通常为:父节点在 i 处,其左子节点必位于数组的 2i 处,其右子节点必位于数组的 2i+1 处。这种以数组表示树的方式,称为隐式表述法。咱们的heap须要动态改变节点数,因此用vector是更好的选择。因为priority queue选择的是binary max heap作为本身的底层机制,因此也只提供了max heap的实现,因此咱们接下里讨论的都是大根堆(max heap)。每一个节点的键值都大于或等于其子节点的键值。dom
为了知足彻底二叉树的条件,最新加入的元素必定要放在最下一层作为叶子节点,并填补在从左至右的第一个空格。新加入的元素并不必定适合于现有的位置,为了知足max-heap的条件,咱们须要对刚加入的元素进行一个上浮(percolate up)的操做:将新节点与其父节点比较,若是其键值比父节点的大,就父子交换位置,交换后新加的元素成为了父节点,此时再与它的父节点比较,如此一直上溯,直到不需对换或直到根节点为止。函数
push_heap函数接受两个随机迭代器,用来表示一个heap底部容器(vector)的头尾,而且新元素已经插入到底部容器的最尾端,才会进入到该函数。若是参数不符合,或并没有新元素插入,该函数的执行结果不可预期。spa
1 template <class RandomAccessIterator> 2 inline void push_heap(RandomAccessIterator first, RandomAccessIterator last) { 3 // 注意,此函数被调用时,新元素应已置于底层容器的最尾端。 4 //便是新元素被加入到数组(本来最后元素的下一位置)后,才调用该函数 5 __push_heap_aux(first, last, distance_type(first), value_type(first)); 6 }
7 template <class RandomAccessIterator, class Distance, class T> 8 inline void __push_heap_aux(RandomAccessIterator first, 9 RandomAccessIterator last, Distance*, T*) { 10 __push_heap(first, Distance((last - first) - 1), Distance(0), 11 T(*(last - 1))); 12 //于上个函数获取到的迭代器所指对象类型T,用于强制转换 13 //Distance(0)为指出最大值的索引值 14 //Distance((last - first) - 1)为指出新添值的索引值 15 }
16 template <class RandomAccessIterator, class Distance, class T> 17 void __push_heap(RandomAccessIterator first, Distance holeIndex, 18 Distance topIndex, T value) { 19 //holeIndex为指出新添值的索引值 20 //topIndex最大值的索引值 21 //value为新添值内容 22 Distance parent = (holeIndex - 1) / 2; // 找出新元素的父节点 23 while (holeIndex > topIndex && *(first + parent) < value) { 24 // 当还没有到达顶端,且父节点小于新值(不符合 heap 的次序特性),继续上浮 25 // 因为以上使用 operator<,可知 STL heap 是一种 max-heap(大根堆)。 26 *(first + holeIndex) = *(first + parent); // 子位置设父值 27 holeIndex = parent; // percolate up:調整新添值的索引值,向上提高至父节点。 28 parent = (holeIndex - 1) / 2; // 获取新索引值的父节点 29 } // 持续到顶端,或知足 heap 的次序特性为止。 30 *(first + holeIndex) = value; // 令最后的索引值为新值,完成插入。 31 }
最大值在根节点处,pop操做取走根节点(实际上是把它转移到vector的尾端节点上),为了知足彻底二叉树的条件,必须割舍最底层最右边的节点,把其值拿出来放至一临时变量里,而后该位置放根节点的值(最后会被pop_back()给移除)。3d
1 template <class RandomAccessIterator> 2 inline void pop_heap(RandomAccessIterator first, RandomAccessIterator last) { 3 __pop_heap_aux(first, last, value_type(first)); 4 } 5 6 template <class RandomAccessIterator, class T> 7 inline void __pop_heap_aux(RandomAccessIterator first, 8 RandomAccessIterator last, T*) { 9 __pop_heap(first, last - 1, last - 1, T(*(last - 1)), distance_type(first)); 10 // 设定准条调整的值为尾值,而后将首值调至 11 // 尾节点(因此以上将迭代器 result 设为 last-1)。而后重整 [first, last-1), 12 // 使之从新成一個合格的 heap。 13 } 14 15 template <class RandomAccessIterator, class T, class Distance> 16 inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last, 17 RandomAccessIterator result, T value, Distance*) { 18 *result = *first; // 设定尾值为首值,因而尾值即为要求的结果, 19 // 可由客端稍后再调用 pop_back() 取出尾值。 20 __adjust_heap(first, Distance(0), Distance(last - first), value); 21 // 以上从新调整 heap,要调整的索引值为 0(亦即树根处),欲调整值为 value(原尾值)。 22 } 23 24 template <class RandomAccessIterator, class Distance, class T> 25 void __adjust_heap(RandomAccessIterator first, Distance holeIndex, 26 Distance len, T value) { 27 //holeIndex:要调整的索引值,从树根处出发 28 //len:全部元素个数(不包括被调整到尾节点的首节点),即[0, len) 29 //value:记录了原最底层最右边的节点的值 30 Distance topIndex = holeIndex; 31 Distance secondChild = 2 * holeIndex + 2; // 调整的索引值的右子节点 32 while (secondChild < len) { 33 // 比较这左右两个子值,而后以 secondChild 表明较大子节点。 34 if (*(first + secondChild) < *(first + (secondChild - 1))) 35 secondChild--; 36 // Percolate down:令较大子值代替要调整索引值处的值,再令调整的索引值下移至较大子节点处。 37 *(first + holeIndex) = *(first + secondChild); 38 holeIndex = secondChild; 39 // 继续找出要调整的索引值的右子节点 40 secondChild = 2 * (secondChild + 1); 41 } 42 if (secondChild == len) { // 相等,说明沒有右子节点(不是说没有,而是被准备pop掉的元素占用了,见*result = *first;),只有左子节点 43 // Percolate down:令左子值代替要调整索引值处的值,再令要调整的索引值下移至左子节点处。 44 *(first + holeIndex) = *(first + (secondChild - 1)); 45 holeIndex = secondChild - 1; 46 } 47 48 // 此时可能还没有知足次序特性,再执行一次上浮操做 49 __push_heap(first, holeIndex, topIndex, value); 50 }
注意,pop_heap以后,最大元素只是被放置于底部容器的最尾端,还没有被取走。若是要取其值,可以使用底部容器的back()函数。若是要移除它,可以使用底部容器所提供的pop_back()函数。code
既然每次调用pop_heap可得到heap中键值最大的元素,若是持续对整个heap作pop_heap操做,且每次的操做范围都从后向前缩减一个元素,那么当整个程序执行完毕,咱们便有了一个递增序列。sort_heap即是如此作的。该函数接受两个迭代器,用来表示heap的首尾。若是并不是首尾,该函数的执行结果不可预期。注意,排序事后,底层容器里的就再也不是一个合法的heap了。对象
1 template <class RandomAccessIterator> 2 void sort_heap(RandomAccessIterator first, RandomAccessIterator last) { 3 // 如下,每执行一次 pop_heap(),极大值即被放在尾端。 4 // 尾端自减后(往左移动一格)再执行一次 pop_heap(),次极值又被放在新尾端。一直下去,最后即得 5 // 排序结果。 6 while (last - first > 1) 7 pop_heap(first, last--); // 每执行 pop_heap() 一次,操做范围即退缩一格。 8 }
这个算法用来将一段现有的数据转化为一个heap。把一段数据转换为heap的要点就是找到最后一个拥有子节点的节点,例如:blog
假设这是一个普通数组,并没有heap特性,那么要想其转化为一个heap,切入点就是找到最后一个拥有子节点的节点,上图而言就是E点,以E节点为首的子树,对该子树进行下沉操做和上浮操做,即先交换E跟J的值,再对J进行上浮操做。这样以E节点为首的子树就符合heap特性了。而后自减,来到了D节点(倒二拥有子节点的节点),一样对以D节点为首的子树进行下沉和上浮操做;而后再自减,直至到达根节点为止。这样整个数组就符合heap特性了。那么问题来了?怎么在一个普通数组中找到最后一个拥有子节点的节点的索引值呢?能够证实的是,若是有一长度为n的数组,那么最后一个拥有子节点的节点的索引值就是 (n - 2) / 2 ,这是数组从索引0开始的状况。排序
1 // 将 [first,last) 排列为一个 heap。 2 template <class RandomAccessIterator> 3 inline void make_heap(RandomAccessIterator first, RandomAccessIterator last) { 4 __make_heap(first, last, value_type(first), distance_type(first)); 5 } 6 7 template <class RandomAccessIterator, class T, class Distance> 8 void __make_heap(RandomAccessIterator first, RandomAccessIterator last, T*, 9 Distance*) { 10 if (last - first < 2) return; // 若是長度為 0 或 1,没必要从新排列。 11 Distance len = last - first; 12 // 找出第一个须要重排的子树头部,以 parent 标示出。因为任何叶子节点都不需执行 13 // perlocate down(下沉),因此有如下计算。 14 Distance parent = (len - 2) / 2; 15 16 while (true) { 17 // 重排以 parent 为首的子树。len 是为了让 __adjust_heap() 判断操做范围 18 __adjust_heap(first, parent, len, T(*(first + parent))); 19 if (parent == 0) return; // 直至根节点,就结束。 20 parent--; // 未到根节点,就将(即将重排的子树的)索引值向前一個节点 21 } 22 }
heap没有迭代器,heap的全部元素都必须遵循特别的排列规则,因此heap不提供遍历功能,也不提供迭代器。