我正在解决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)之类的节点,如何搜索随机样本中的“最近”节点帮助找到正确的节点?
答案 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值为零)来执行此操作,并使用这些值做出相同的决定二进制搜索将具有的内容。
我会在这里停留,以免破坏您的乐趣。