STL源码剖析笔记——迭代器
二叉堆(Binary Heap)是一种树状数据结构,它满足堆的性质,即父节点的值与其子节点的值之间存在特定的关系。一个array和一组heap算法组成一个heap,同时需要满足完全二叉树的条件。通常,二叉堆有两种形式:最大堆(Max Heap)和最小堆(Min Heap)。
最大堆: 在最大堆中,任意节点的值都不大于其父节点的值。也就是说,根节点是堆中的最大元素。
最小堆: 在最小堆中,任意节点的值都不小于其父节点的值。也就是说,根节点是堆中的最小元素。
二叉堆通常通过数组来实现,其中树的每个节点的值存储在数组中的一个位置上。对于任意节点 i:
其左子节点在位置 2i;
其右子节点在位置 2i + 1;
其父节点在位置 i / 2。
array的缺点是无法动态改变大小,而heap却需要这项功能,因此,以vector代替array是更好的选择。 heap没有迭代器。
为了满足完全二叉树的条件,新加入的元素一定要放在最下一层作为叶节点,并填补在由左至右的第一个空格,也就是把新元素插入在底层vector的end()处。新元素是否适合于其现有位置呢?为满足最大堆的条件,将新节点拿来与其父节点比较,如果其键值比父节点大,就父子对换位置。 如此一直上溯,直到不需对换或直到根节点为止。
既然身为最大堆,最大值必然在根节点。pop操作取走根节点之后(其实是移至底部容器vector的最后一个元素),为了满足完全二叉树的条件,必须将最下一层最右边的叶节点拿掉,现为这个被拿掉的节点找一个适当的位置。为满足最大堆的条件,我们将根节点(最大值被取走后,形成一个“洞”)填入上述那个失去生存空间的叶节点值,再将它拿来和其两个子节点比较键值,并与较大子节点对调位置。如此一直下放,直到这个“洞”的键值大于左右两个子节点,或直到下放至叶节点为止。
既然每次 pop_heap 可获得heap中键值最大的元素,如果持续对整个heap 做pop_heap操作,每次将操作范围从后向前缩减一个元素(因为pop_heap会把键值最大的元素放在底部容器的最尾端),当整个程序执行完毕时,我们便有了一个递增序列。排序过后,原来的heap就不再是一个合法的heap了。
这个函数的作用就是将一组数据转化为一个heap,主要流程是从最后一个非叶子节点开始,与其子节点进行比较、交换为一次循环,每次循环结束将数组总长度 - 1,重新计算最后一个非叶子节点,进行下一次循环,到根节点为止。
对于一个长度为n的数组来说,若最后一个非叶子节点的索引为i,则他的右子节点要在数组范围之内,即2i + 1 < n,那么i < (n - 1)/2,所以最后一个非叶子节点的索引为(n - 1)/2 - 1。举一个例子说明:
设初始数组为: {4, 10, 3, 5, 1, 7, 9}
第一次迭代:
从最后一个非叶子节点开始,(7 - 1)/2 - 1 = 2,即索引 2 对应的元素 3。
对元素3进行下沉操作,找到其左右子节点中的最大者9,与之交换。
数组变为 {4, 10, 9, 5, 1, 7, 3},此时数组总长度变为7 - 1 = 6。
第二次迭代:
继续从最后一个非叶子节点开始,,(6 - 1)/2 - 1 = 1.5,即索引 1 对应的元素10。
对元素10进行下沉操作,找到其左右子节点中的最大者,即为10,无需交换。
数组不变,仍为{4, 10, 9, 5, 1, 7, 3}。
第三次迭代:
继续从最后一个非叶子节点开始,(5 - 1)/2 - 1 = 1即索引 1 对应的元素 10,和第二次迭代情况一样。
数组不变,仍为{4, 10, 9, 5, 1, 7, 3}。
第四次迭代:
继续从最后一个非叶子节点开始,(4 - 1)/2 - 1 = 0.5即索引 0 对应的元素 4。
对元素4进行下沉操作,找到其左右子节点中的最大者10,与之交换,继续向下交换;找到其左右子节点中的最大者5,与之交换。
数组变为{10, 5, 9, 4, 1, 7, 3}。
最终数组 {10, 5, 9, 4, 1, 7, 3} 成功转化为最大堆。
这个过程中,我们每次都从最后一个非叶子节点开始,对其进行下沉操作,确保每个节点的子树都满足最大堆的性质。整个过程的时间复杂度是 O(n),其中 n 是数组的长度。
priority_queue是一个拥有权值观念的queue,它允许加入新元素、移除旧元素、审视元素值等功能。同样是一个容器配接器,不允许遍历,没有迭代器,通过一个vector和heap算法作为底层容器来实现。由于这是一个queue,所以只允许在底端加入元素,并从顶端取出元素,除此之外别无其它存取元素的途径。priority_queue带有权值观念,其内的元素并非依照被推入的次序排列,而是自动依照元索的权值排列(通常权值以实值表示)。权值最高者,排在最前面。