根据cppreference.com std::discrete_distribution
界面要求库开发人员实施probabilities()
和template<class Generator> result_type operator()(Generator& g, const param_type& params);
。后者未在cppreference.com上记录,但根据libc++ implementation,它允许用户从给定的权重序列的子序列中进行采样(并使用传递的生成器作为另一个生成器的熵源,但它不相关现在)。我已阅读N3551(关于std::discrete_distribution
的唯一可提出的提案),并且它没有为std::discrete_distribution
提供此类界面的任何理由。
问题是这样的接口只允许一个合理的实现,称为"roulette wheel selection"(需要一次调用随机数生成器和O(log(N))
数组查找进行二分查找)。另一种算法,称为"alias method"(需要两次调用随机数生成器和一次数组查找),不能用于实现此接口(如果我们将存储相应的,probabilities()
可以实现单独数组中的概率,但它有点不公平和低效:),因为如果需要从子序列中进行采样,则不能使用别名方法。
答案 0 :(得分:2)
对于给定随机分布构造参数对象的成本,以及其内部表示的性质,没有要求。或其他任何东西。特别地,参数对象不必是并且通常不仅仅是其构造参数的向量。构造参数对象必须执行任何必要的预计算/预处理,以便稍后生成分布。 (这不仅适用于discrete_distribution
。有许多分布,其自然参数需要预先计算步骤的非平凡成本。)
为了实现“轮盘赌”算法,参数对象构造函数必须将参数转换为累积分布函数(CDF)。但是,该参数对象没有用,因为正如您所说,它预计每次调用的执行时间为O(log n),标准需要分摊O(1)。可以使用替代随机算法,它具有随机O(1)时间,我认为这是合格的;它还需要对参数进行O(n)预处理步骤,以便提取最大值。
别名方法,在我看来是最优的,需要更复杂的O(n)预处理步骤,但正如我之前所说,对参数对象的构造成本没有要求。 (计算出的别名概率对象的大小也是O(n);我使用的实现将n舍入为2的幂,因此它的最坏情况大小小于32n字节。我没有查看任何标准库实现。)
顺便说一下,别名方法不需要两个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)。所以它符合所有要求。