解决此问题的一种方法是重新考虑如何构建包含累积总数的二叉搜索树。与其构建二叉搜索树,不如考虑将每个节点解释如下:
- 每个节点存储一系列专用于节点本身的值。
- 左子树中的节点表示从该范围左侧的概率分布中采样。
- 右子树中的节点表示从该范围右侧的概率分布中采样。
例如,假设我们对于事件 A、B、C、D、E、F 和 G 的权重分别为 3、2、2、2、2、1 和 1。我们构建这个包含 A、B、C 的二叉树, D、E、F 和 G:
D
/ \
B F
/ \ / \
A C E G
现在,我们用概率注释树。由于 A、C、E 和 G 都是叶子,我们给它们每个概率质量一个:
D
/ \
B F
/ \ / \
A C E G
1 1 1 1
现在,看看 B 的树。B 被选中的权重为 2,A 被选中的权重为 3,C 被选中的概率为 2。如果我们将它们归一化到范围 [0, 1),那么 A 占概率的 3/7,B 和 C 各占 2/7s。因此,我们有 B 的节点表示 [0, 3/7) 范围内的任何内容都进入左子树,范围 [3/7, 5/7) 范围内的任何内容都映射到 B,范围 [5 /7, 1) 映射到右子树:
D
/ \
B F
[0, 3/7) / \ [5/7, 1) / \
A C E G
1 1 1 1
类似地,让我们处理 F。E 被选中的权重为 2,而 F 和 G 的被选中概率权重为 1。因此这里E的子树占概率质量的1/2,节点F占1/4,G的子树占1/4。这意味着我们可以将概率分配为
D
/ \
B F
[0, 3/7) / \ [5/7, 1) [0, 1/2) / \ [3/4, 1)
A C E G
1 1 1 1
最后,让我们看看根。左子树的组合权重为 3 + 2 + 2 = 7。右子树的组合权重为 2 + 1 + 1 = 4。D 本身的权重为 2。因此左子树的概率为 7/13被选中,D 有 2/13 的概率被选中,右子树有 4/13 的概率被选中。因此,我们可以将概率最终确定为
D
[0, 7/13) / \ [9/13, 1)
B F
[0, 3/7) / \ [5/7, 1) [0, 1/2) / \ [3/4, 1)
A C E G
1 1 1 1
要生成随机值,您将重复以下操作:
- 从根开始:
- 在 [0, 1) 范围内选择一个均匀随机值。
- 如果它在左子树的范围内,则下降到它。
- 如果它在右子树的范围内,则下降到它。
- 否则,返回当前节点对应的值。
概率本身可以在构建树时递归确定:
- 任何叶节点的左右概率均为 0。
- 如果一个内部节点本身的权重为 W,其左树的总权重为 W L,其右树的总权重为 W R,那么左边的概率为 (W L ) / (W + W L + W R ),右边的概率为概率是 (W R ) / (W + W L + W R )。
这种重新表述有用的原因是,它为我们提供了一种在 O(log n) 时间内更新每个概率更新概率的方法。特别是,让我们考虑一下,如果我们更新某个特定节点的权重,哪些不变量会发生变化。为简单起见,我们现在假设节点是叶子。当我们更新叶节点的权重时,叶节点的概率仍然是正确的,但对于它正上方的节点却是不正确的,因为该节点的一个子树的权重发生了变化。因此,我们可以(在 O(1) 时间内)通过使用与上述相同的公式重新计算父节点的概率。但是随后该节点的父节点不再具有正确的值,因为它的子树权重之一发生了变化,因此我们也可以在那里重新计算概率。这个过程一直重复到树的根部,我们对每个级别进行 O(1) 计算以纠正分配给每个边的权重。假设树是平衡的,因此我们必须做 O(log n) 的总工作来更新一个概率。如果节点不是叶节点,则逻辑相同;我们只是从树的某个地方开始。
简而言之,这给出了
- O(n) 时间来构建树(使用自下而上的方法),
- O(log n) 时间来生成一个随机值,并且
- O(log n) 时间来更新任何一个值。
希望这可以帮助!