Algorithm std::离散分布接口背后的原因

Algorithm std::离散分布接口背后的原因,algorithm,c++11,random,stl,random-sample,Algorithm,C++11,Random,Stl,Random Sample,根据cppreference.comstd::discrete_distribution接口要求库开发人员实现probabilities()和模板结果类型操作符()(生成器和g、常量参数类型和参数)。后者没有在cppreference.com上记录,但据报道,它允许用户从给定权重序列的子序列中采样(并使用传递的生成器作为另一个生成器的熵源,但现在已经无关紧要了)。我已经阅读了(关于std::discrete_distribution)的唯一googleable建议,但它没有为std::discr

根据cppreference.com
std::discrete_distribution
接口要求库开发人员实现
probabilities()
模板结果类型操作符()(生成器和g、常量参数类型和参数)。后者没有在cppreference.com上记录,但据报道,它允许用户从给定权重序列的子序列中采样(并使用传递的生成器作为另一个生成器的熵源,但现在已经无关紧要了)。我已经阅读了(关于
std::discrete_distribution
)的唯一googleable建议,但它没有为
std::discrete_distribution
提供这样一个接口的任何理由


问题是这样一个接口只允许一个合理的实现,称为(需要调用一个随机数生成器和
O(log(N))
array lookup进行二进制搜索)。另一种称为(需要两次调用随机数生成器和一次数组查找)的算法不能用于实现此接口(如果我们将相应的概率存储在单独的数组中,则可以实现
probabilities()
,但这有点不公平和低效:),因为如果我们需要从子序列中采样,就不能使用别名方法。

对于为给定的随机分布构造参数对象的成本以及其内部表示的性质都没有要求。或者别的什么。特别是,parameters对象不一定是,而且通常不仅仅是其构造参数的向量。构造参数对象必须执行任何必要的预计算/预处理,以使下一代分布有效。(这不仅适用于
离散分布
。有许多分布的自然参数需要非平凡成本的预计算步骤。)

为了实现“轮盘赌”算法,参数对象构造函数必须将参数转换为累积分布函数(CDF)。但是,该parameters对象将不会有用,因为正如您所说的,它预期每次调用的执行时间为O(logn),并且标准要求摊销O(1)。可以使用另一种随机算法,它具有随机O(1)时间,我认为这是合格的;它还需要对参数进行O(n)预处理,以提取最大值

alias方法在我看来是最优的,它需要更复杂的O(n)预处理步骤,但正如我前面所说的,对parameters对象的构造成本没有要求。(计算出的别名概率对象的大小也是O(n);我使用的实现将n取整为2的幂,因此其最坏情况下的大小小于32n字节。我没有看过任何标准库实现。)

顺便说一下,alias方法不需要两个PRNG调用。用一个来实现它是非常常见的:给定n个可能的结果,将PRNG的范围划分为n个离散的部分(这就是为什么我将n取整为2的幂);每个工件的阈值基于原始随机数。如果使用模算子将其分割成离散的片段,则阈值与可能的随机数的整个范围成比例;如果按范围划分,则阈值与长方体的大小成比例,并由长方体的原点偏移。无论哪种方式,都会使用相同的随机数来选择框,然后选择两个别名中的一个,生成函数大致包括:

auto r = prng();
size_t b = box_select(r);
return r > threshold[b] ? alias1[b] : alias2[b];

这不仅仅是摊销O(1);它是O(1)。所以它满足了所有的要求。

问题是什么?@TimothyShields我在问为什么标准接口的实现方式不允许更高效的实现(
O(log(N))
vs
O(1)
)。为什么不直接使用它呢?请特别参阅第3节。@TimothyShields它不保证对RNG的调用次数恒定。从“复杂性:对
g.operator()
的摊销常量调用次数”页开始(重点添加)预期的常数复杂性在实践中相当于摊销的常数复杂性。我不太确定相同的随机数
r
是否可以用于框选择和阈值检查。因为我们基本上是在笛卡尔坐标系中选择一个点,
x
坐标用于选择框,
y
用于选择两个别名中的一个。@KostyaBazhanov:在算法的最简单理论描述中,您需要两个随机变量,一个在[0,Nbox]范围内,另一个在[0,Nbox]范围内[0,∑权重)。在我见过(或写过)的所有实际实现中,使用了一个随机变量,有效范围为[0,LCM(Nbox,∑权重))。如果您可以生成64位随机变量,则可以将权重四舍五入到32位,并将N增加到下一个2的幂。这样,您就可以仅使用位掩码从单个随机数中获取两个值。因为∑权重通常不是2的幂,所以您偶尔必须拒绝…随机变量,但具有如上所述的约束,以及总的来说,盒子的数量不是几百万,拒绝率非常小,因此在分析中可以忽略。我对任务感兴趣:)轮盘赌和alias方法之间的差异在样本数量较少的情况下几乎看不到。以下是原因()我说的是对PRNG的两次调用。@KostyaBazhanov:我知道你在说什么。但是你不需要两次调用PRNG,老实说。我已经试着解释了如何做;剩下的唯一一件事就是实际粘贴代码,如果我觉得无聊,我可能会这么做。但是所有的提示都在那里。:)