Randomized Heap / 随机堆
hanbingqigu · · 算法·理论
Randomized Heap / 随机堆
——一种简单易实现的可并堆
介绍
以下所说的堆默认为小根堆。
随机堆是一种可并堆,可以在期望
- 合并两个堆
- 添加元素
- 删除最小值(堆顶元素)
- 减少某个元素的值(要求知道这一元素的节点位置)
- 移除某一特定元素(要求知道这一元素的节点位置)
- 查询最小值(
\mathcal O(1) )
在实现上,随机堆的实现难度较为容易,略易于左偏树等常见可并堆。
操作
随机堆的核心操作是合并(merge):
算法 1. 合并
设当前要合并分别以
x,y 为根的堆\mathcal P,\mathcal Q 。不妨设x 的值小于y 的值,因此x 将充当新堆的根。随机选取x 的左子树或者右子树之一,记作堆\mathcal T ,将\mathcal T 与\mathcal Q 合并作为x 新的左子树或右子树(取决于之前的选取)。重复以上算法直到\mathcal P, \mathcal Q 其中之一为空堆。
int merge(int x, int y) {
if (!x || !y) return x ^ y;
if (t[x].val > t[y].val) swap(x, y);
int& son = rnd() & 1 ? t[x].ls : t[x].rs;
son = merge(son, y);
t[son].fa = x;
return x;
}
实际使用中,应使用质量较高的随机数生成器,比如 std::mt19937 而不是 rand()。
有了合并操作,其他操作是平凡的。
算法 2. 添加元素
将新的元素视作单点堆,合并原堆与新元素的堆作为新堆。
算法 3. 删除堆顶
合并根的左子树和右子树作为新堆。
算法 4. 减少元素的值
要求已知该元素对应的节点为
x 。断开x 和父亲的边,直接修改x 节点的值,合并 以x 为根的堆和原堆作为新堆。算法 5. 移除元素
将该元素的值减少为
-\infty ,这将会使其移动到堆顶。删除堆顶。
以上操作的复杂度全部基于合并操作的复杂度,为期望
算法 6. 查询最小值
直接查询根的值即可。复杂度为
\mathcal O(1) 。
复杂度分析
一个含有
下面给出时间复杂度的证明。
引理
对于任意一棵
N 个节点的二叉树\mathcal T 。从根节点出发,每步随机走左儿子或右儿子(即使是空节点),令首次到达空节点的期望步数为E[\mathcal T] ,则:E[\mathcal T] \le \log_2(N + 1) 证明
采用归纳法,当
N = 0 时命题显然成立。假设对于所有的
N_0 \le N - 1 命题成立。对于一棵N 个节点的二叉树\mathcal T ,令其左子树为\mathcal L ,右子树为\mathcal R ,节点个数分别为A,B 。则有期望递推:E[\mathcal T] = \frac 1 2(E[\mathcal L] + E[\mathcal R]) + 1 由于
A,B \le N - 1 ,应用归纳假设得到:\begin{align} E[\mathcal T] &\le \frac 1 2(\log_2(A+1) + \log_2(B+1)) + 1 \\ &= \frac 1 2 \log_2[(A+1)(B+1)] + 1 \\ &= \log_2\sqrt{(A+1)(B+1)} + \log_2 2 \\ &= \log_2 2\sqrt{(A + 1)(B + 1)} \end{align} 显然
A + B = N - 1 ,由基本不等式得:2\sqrt{(A + 1)(B + 1)} \le A + B + 2 = N + 1 因此:
E[\mathcal T] \le \log_2(N+1) 可见命题对于
N 也成立,归纳得对于所有的N \in \N^* 命题成立。\blacksquare
上述引理还有一个有趣的性质是:当且仅当
借助引理,我们就能给出随机堆时间复杂度的证明:
定理
随机堆合并操作的复杂度为期望
\mathcal O(\log n) 。证明
考察合并操作时两个堆根的变化,这本质上是在两棵二叉树上从根节点出发做随机游走。每递归一层,其中一棵树的当前节点将会向下走一步变为新的堆根,直到其中一个节点走到空节点。根据引理,其期望步数小于为
\mathcal O(\log n) ,因此合并操作的复杂度为\mathcal O(\log n) 。\blacksquare
由于复杂度不是均摊的,因此随机堆是可持久化的。