1. 定义

  1. [max(min) tree] 一棵树, 其中每个节点的值都大于 (小于) 或等于其 children (如果有) 的值.
  2. [max(min) heap] max(min) tree + complete binary tree.

2. 性质

  1. heap 是一种 隐式数据结构 (implicit data structure).

    完全二叉树 表示的 heap 在数组中 隐式储存 (没有明确的指针或其他数据能够用来重塑这种结构).

    由于没有储存 结构信息, 这种表示方法的空间利用率很高 (没有浪费任何空间); 又由于用数组形式储存, 它的时间效率也很高.
  2. 由于是 完全二叉树, 自然满足如下性质:
    1. 若一颗 complete binary tree 有 \(n\) 个元素, 那么它的高度为 \(\log_2 (n+1)\) .
    2. 设一颗 complete binary tree 中一元素的编号为 \(i\) , \(1 \leq i \leq n\) , 则有以下关系:
      • 若 \(i = 1\) , 则该元素为二叉树的 root; 若 \(i>1\) , 则其 parent 的编号为 \(\lceil i/2 \rceil\) .
      • 若 \(2i>n\) , 则该元素无 left child; 否则, 其 left child 的编号为 \(2i\) .
      • 若 \(2i+1>n\) , 则该元素无 right child; 否则, 其 right child 的编号为 \(2i+1\) .

3. 大根堆的 pop & remove

3.1. 核心逻辑 & 发现问题

不失一般性, 存在如下图一个大根堆.

假设现在要删除 heap[current], 那么我们要做的核心步骤是:

  • heap[current] 的 children 中较大的那个 (我们假设那个结点是 heap[child] ) 移动到 heap[current] 的位置;

这时 heap[child] 相当于 "空" 的状态,

因此顺理成章地利用递归或迭代, 再把 heap[child] 当作新的 heap[current] 而反复执行核心步骤.

应当将 child 小于 heapSize 作为限制 iteration 或 recursion 继续进行的条件.

然而, 如果我们使用上述逻辑 pop 上图中的 heap[1]:

  1. 48 填入 heap[1];
  2. 43 填入 heap[2];
  3. 30 填入 heap[4];
  4. heap[8] 为空;

为满足 定义 1 和 定义 2, 此时若将 lastElement = 41 填入 heap[8], 则 heap[4] = 30 小于 heap[8] = 41, 与 定义 1 产生冲突.

为什么会出现这样的问题呢?

根据 定义 1:

[max(min) tree] 一棵树, 其中每个结点的值都大于(小于)或等于其 children (如果有)的值.

我们应该明确一点:

  • 整体上, 大根树中下层元素不一定大于上层元素, 而只是每个结点要大于自己所有的 descendent.

如上图, 尽管 heap[11] 在 level 4; 但仍比处于 level 2 的 heap[3] 大.

这就是为什么在 pop 或者 remove 时, 我们需要更加复杂的方法进行 重构.

3.2. 重构大根堆

2.1. 提出的问题不建议使用 recursion 解决; 因为如果用 iteration, 只要在循环体中添加一步简单的判断就即可.

再次观察前文的大根堆:

我们发现, 冲突的产生本质上是因为,

尽管 heap[4] 是 silblings 中较大的那个, 但它的 child heap[8] 并没有其 sibling (也就是 heap[5]) 的 child heap[11] = lastElement 大.

那么, 我们只要在保持核心逻辑的同时, 一旦发现 heap[current] 的较大 child 比 lastElement 小, 那就结束循环, 把 lastElement 填入 heap[current]! 后面的就不用管了!

如果没找到......那就继续循环, 循环到底, 把 lastElement 填到最下面就好.

3.3. 代码实现

  1. pop最顶端元素(root).
template<class T>
void maxHeap<T>::pop() {
if (heapSize == 0) throw queueEmpty();
// Delete the largest element.
heap[1].~T();
// Delete the last element and re-construct the heap.
T lastElement = heap[heapSize];
heap[heapSize--].~T();
// Starting from the root, find location for the last element.
int current = 1,
child = 2;
// Loop until the end.
while (child <= heapSize) {
// "child" should be the larger child of "current"
if (child < heapSize && heap[child] < heap[child + 1]) { child++; }
// IF: "current" points to a node whose child is smaller than "lastElement",
// indicating that "lastElement" can be put in "heap[current]".
if (lastElement >= heap[child]) {
// End loop.
break;
}
// OTHERWISE.
else {
heap[current] = heap[child];
current = child;
// Move "child" to its child.
child << 1;
}
}
heap[current] = lastElement;
}
  1. remove下标为 i 的元素.
template<class T>
T maxHeap<T>::remove(int i) {
if (heapSize == 0) throw queueEmpty;
T theDeleted = heap[i];
T lastElement = heap[heapSize];
heap[heapSize].~T();
int current = i, child = i << 1;
while (child <= heapSize) {
// Make sure "child" points to the larger one between the sublings.
if (child < heapSize && heap[child] < heap[child + 1]) {
child++;
}
// IF: "current" points to a node whose child is smaller than "lastElement",
// indicating that "lastElement" can be put in "heap[current]".
if (lastElement >= heap[child]) {
// End loop.
break;
}
// OTHERWISE.
else {
heap[current] = heap[child];
current = child;
child <<= 1;
}
}
heap[current] = lastElement;
return theDeleted;
}

3.4. 复杂度分析

对于方法 pop, 遍历只迭代至 left/right child, 因此时间复杂度为:

\[O(\log{n})
\]

方法 remove 的复杂度取决于目标 node 的 descendent 的数量.

4. Initialize

4.1. 逻辑分析

root 从最后一个 node 的 parent node 开始向根迭代, 直至 max heap 真正的根;

对于迭代中的每个 root, child 从其 child node 开始向 descendent 迭代 (迭代需要保证 child 指向 siblings 中较大的那个):

  • rootElement 小于 child 的元素, 则 child 继续向 descendent 迭代, 同时将 child 的元素覆盖至 child/2;
  • rootElement 大于等于 child 的元素, 则 child 终止迭代, 同时将 rootElement 存入 child/2;

4.2. 代码实现

template<class T>
void maxHeap<T>::initialize(T* theHeap, int theSize)
{
delete[] heap; // Empty the memory of "T* maxHeap<T>::heap".
heap = theHeap; // Make "heap" points to "theHeap".
heapSize = theSize; // Set the "heapSize". // 'root' would iterates from {heapSize/2 (parent of the last element)} and keep decreasing until reaches real root.
for (int root = heapSize / 2; root >= 1; root--) {
// Pick up the current element of 'root'.
T rootElement = heap[root];
int child = 2 * root;
// 'child' iterates from the child of current root to end, but cannot be larger than 'heapSize'.
for (; child <= heapSize; child *= 2) {
// Ensure 'heap[child]' is the larger one between the siblings.
if (child < heapSize && heap[child] < heap[child + 1]) { child++; }
if (rootElement >= heap[child]) { // IF: 'rootElement' can be put in 'heap[child/2]'.
break;
}
// IF: "rootElement" cannot be put in "heap[child/2]".
// Move 'heap[child]' to 'heap[child/2]'.
heap[child/2] = heap[child];
// Re-allocate the next level to 'child'.
}
heap[child / 2] = rootElement;
}
}

4.3. Complexity

假设元素个数为 \(n\), 高度为 \(h\).

  • 由于外层 for 循环 root 从 \(n/2\) 开始迭代, 因此从元素上看总共迭代了 \(n/2\) 次, 从层数上看总共迭代了 \(h-1\) 层.
  • 内层 for 循环的每次迭代, 实际上都遍历了一棵 complete binary tree, 复杂度为 \(O(h_{i})\). 其中 \(h_{i}\) 是该层 complete binary tree 的高度.
  • binary tree 的第 \(j\) 层, 最多有 \(2^{j-1}\) 个 node; 而每个 node 的高度 \(h_{i}=h-j+1\).
\[O(\sum_{j=1}^{h-1}{2^{j-1}(h-j+1)}) = O(\sum_{k=2}^{h}{2^{h-k}k})=O(2^h\sum_{k=2}^{h}{\frac{k}{2^k}})=O(2^h)
\]

因为有 \(n\) 个元素的 complete binary tree 的高度为 \(h= \lceil \log_{2}{(n+1)} \rceil\), 因此:

\[O(2^h)=O(2^{\lceil \log_{2}{(n+1)} \rceil})=O(n)
\]

由于外层 for 循环从元素上看迭代了 \(n/2\) 次, 所以复杂度下限为 \(\Omega(n)\).

综上, 方法 initialize 的复杂度为:

\[\Theta(n)
\]

Reference | Data Structures, Algoritms, and Applications in C++, Sartaj Sahni

大根堆的pop&remove&initialize的更多相关文章

  1. CJOJ 2482 【POI2000】促销活动(STL优先队列,大根堆,小根堆)

    CJOJ 2482 [POI2000]促销活动(STL优先队列,大根堆,小根堆) Description 促销活动遵守以下规则: 一个消费者 -- 想参加促销活动的消费者,在账单下记下他自己所付的费用 ...

  2. poppo大根堆的原理与实现。

    大根堆的定义:1 大根堆是一个大根树 2 大根堆是一个完全二叉树 所以大根堆用数组表示是连续的,不会出现空白字段. 对于大根堆的插入 对于大根堆的插入,可以在排序前确定大根堆的形状,可以确定元素5从位 ...

  3. bzoj 1577: [Usaco2009 Feb]庙会捷运Fair Shuttle——小根堆+大根堆+贪心

    Description 公交车一共经过N(1<=N<=20000)个站点,从站点1一直驶到站点N.K(1<=K<=50000)群奶牛希望搭乘这辆公交车.第i群牛一共有Mi(1& ...

  4. 51nod K 汽油补给 大根堆+小根堆....

    题目传送门 用优先队列瞎搞... 想着在每个地方 先算上一个点到这一个点要花费多少钱 这个用小根堆算就好 然后在这个地方加油 把油钱比自己多的替代掉 这个用大根堆维护一下 然后两个堆之间信息要保持互通 ...

  5. bzoj 5495: [2019省队联测]异或粽子【可持久化trie+大根堆】

    和bzoj4504差不多,就是换了个数据结构 像超级钢琴一样把五元组放进大根堆,每次取一个出来拆开,(d,l,r,p,v)表示右端点为d,左端点区间为(l,r),最大区间和值为v左端点在p上 关于怎么 ...

  6. bzoj 4504: K个串【大根堆+主席树】

    像超级钢琴一样把五元组放进大根堆,每次取一个出来拆开,(d,l,r,p,v)表示右端点为d,左端点区间为(l,r),最大区间和值为v左端点在p上 关于怎么快速求区间和,用可持久化线段树维护(主席树?) ...

  7. TJOI2013 奖学金—大根堆实现(洛谷P3963)

    奖学金 题目描述 小张学院有 \(c\) 名学生,第 \(i\) 名学生的成绩为 \(ai\) ​,要获得的奖学金金额为 \(bi\) . 要从这 \(c\) 名学生中挑出 \(n\) 名学生发奖学金 ...

  8. priority_queue()大根堆和小根堆(二叉堆)

    #include<iostream> #include <queue> using namespace std; int main() { //对于基础类型 默认是大顶堆 pr ...

  9. AtCoder Beginner Contest 249 F - Ignore Operations // 贪心 + 大根堆

    传送门:F - Keep Connect (atcoder.jp) 题意: 给定长度为N的操作(ti,yi). 给定初值为0的x,对其进行操作:当t为1时,将x替换为y:当t为2时,将x加上y. 最多 ...

随机推荐

  1. Codeforces Round #783 (Div. 2)

    A. Direction Change 题意 从(1,1)点出发到(n,m),每次可以向上下左右四个方向移动,但是不能与上次移动方向相同 最少要移动多少不,如果不能到达输出 -1 思路 假设n< ...

  2. 零基础学Java(3)运算符

    运算符 运算符用于连接值.Java提供了一组丰富的算术和逻辑运算符以及数学函数. 算术运算符 在Java中,使用算术运算符+.-.*./表示加.减.乘.除运算.当参与/运算的两个操作数都是整数时,表示 ...

  3. efcore在Saas系统下多租户零脚本分表分库读写分离解决方案

    efcore在Saas系统下多租户零脚本分表分库读写分离解决方案 ## 介绍 本文ShardinfCore版本x.6.0.20+ 本期主角: - [`ShardingCore`](https://gi ...

  4. 第十天python3 函数的销毁

    全局函数销毁 三种方式: 1.重新定义同名函数 2.del语句删除函数对象 3.程序结束时 局部函数销毁 三种方式: 1.重新在上级作用域定义同名函数: 2.del语句删除函数对象: 3.上级作用域销 ...

  5. 关于2022年3月9日之后Typora登录不了--已解决

    p.s.今天是2022.7.27,软件版本:13.6.1 (以下所有方法,亲自尝试后整理出的) 报错信息: This beta version of typora is expired, please ...

  6. python在执行命令时添加环境变量或指定执行路径

    cwd: 命令的执行路径,相当于os.chdir('/home')提前切换到对应路径 env: 环境变量,某些执行路径需要添加必须的环境变量,例如fastboot依赖与adb路径下的环境变量 impo ...

  7. [Linux] 如何在 Linux 电脑上制作专业的视频教程

    目录 前言 1.软件工具准备 a. 录音软件 b. 录屏软件 c. 摄像头软件 d. 安卓屏幕操作软件 e. 视频剪辑软件 2.视频教程制作 3.效果 参考链接 前言 博主使用 Arch Linux ...

  8. 6.11 NOI 模拟

    \(T1\)魔法师 \(f(x)\)是各个数位之积,当\(f(x)\ne 0\),每一位只能是\(1\sim 9\),考虑数位积的质因数分解只能是\(2,3,5,7\)的形式,考虑对所有的\((a,b ...

  9. 5.4 NOI模拟

    \(5.4\ NOI\)模拟 \(T1\) 想到分讨,但是暴力输出一下方案之后有很多特别的情况要讨论,就弃了... 假设\(a\)是原序列,\(b\)是我们得到的序列 设\(i\)是最长公共前缀,\( ...

  10. DP选讲

    $DP$选讲直接上题吧放个题单[各省省选DP](https://www.luogu.com.cn/training/151079)$P5322[BJOI2019]$排兵布阵一眼题,考虑$dp[i][j ...