来自离散概率分布算法的采样

时间:2019-05-10 18:16:50

标签: algorithm

我正在解决Sedgewick书中的一项任务:

  

用构造函数编写一个类Sample,该构造函数的数组为p []   将值加倍作为参数,并支持以下两项操作:   random()—以概率p [i] / T(其中T是   p []中的数字之和

我认为有一个简单的解决方案:将所有边界值存储在数组中并找到比随机样本低的第一个值,例如,我们有(值,权重)对:(1,10.0),(2, 20.0),(3、10.0),(4、10.0)。我们将其转换为 (1、0.0),(2、10.0),(3、30.0),(4、40),对随机值[0-50](例如35)进行采样,发现其> 30,因此答案为' 3'。

但是在书中有一个建议:

  

使用完整的二叉树,其中每个节点的隐含权重为p [i]。   在每个节点中存储其节点中所有节点的累计权重   子树。要生成随机索引,请选择0之间的随机数   和T并使用累积权重来确定   要探索的子树。

我在github上看到了这个解决方案:https://github.com/reneargento/algorithms-sedgewick-wayne/blob/master/src/chapter2/section4/Exercise35_Sampling.java

但是我不明白它为什么起作用:我们不会代表范围,而是会有一些树,这些树将具有(3,10),(4,10)之类的节点,如何搜索随机样本中的“最近”节点帮助找到正确的节点?

1 个答案:

答案 0 :(得分:1)

您的想法是正确的,但还不完全正确。您想做inverse transform sampling。您正在考虑的逐步函数是给定离散分布的逆累积密度函数(cdf)。更传统的做法是在间隔[0..1)上在X轴上写入搜索值。权重分别为1、2、3和4的权重分别为1 / 5、2 / 5、1 / 5、1 / 5。您想要将间隔分为该大小的片段,并将这些间隔映射到各自的值: / p>

[0   .. 1/5) ->  1   // Note interval widths are 1/5,2/5,1/5,1/5 as desired.
[1/5 .. 3/5) ->  2
[3/5 .. 4/5) ->  3
[4/5 ..   1) ->  4

正如您所说,将间隔的顶部及其值存储在数组中就足够了。在C中,

struct IntervalTop {
  double r;
  int value;
} cdf[] = {{.2, 1}, {.6, 2}, {.8, 3}, {1.0, 4}};

现在在[0..1)中生成一个随机值,并查找相应的子间隔以确定该值。例如,在第一个间隔中为0.1,因此返回1。在第三个间隔中,返回0.7,因此返回3。对于启动器来说,简单的线性搜索就可以了:

double r = ... // Compute random double 0.0 <= r < 1.0 .
for (int i = 0; ; ++i)
  if (cdf[i].r > r) 
     return cdf[i].value;

但是,搜索时间随着间隔的数量而增加。

提高性能的一种简单方法是用二进制搜索替换循环。然后搜索时间随着间隔数的对数增长。

但是Sedgewick希望您能更努力地工作,大概是出于学习目的。

他的建议还具有运行时间O(log(n)),但更为复杂。他说的是使用完整的二进制搜索树。每个节点都包含以该节点为根的子树中的值,权重(w)以及所有权重(t)的总和。所以对于这个问题,...

                  ____3(w=1/5,t=1)____
                 /                     \
        2(w=2/5,t=3/5)           4 (w=1/5,t=1/5)
        /
1(w=1/5,t=1/5)

实际上,您不需要算法的权重(这就是S表示它们“隐式”的原因),但是在此处包括它们可以使您更轻松地了解正在发生的事情。

您将如上所述在[0..1)中生成一个随机数r,但是在这里,您将使用r的值作为参考来搜索树。

您将通过查看tree.t,tree.left.t和tree.right.t(缺少的子代等于.t值为零)来执行此操作,并使用这些值做出相同的决定二进制搜索将具有的内容。

我会在这里停留,以免破坏您的乐趣。