从C ++ 11开始,有许多std随机数引擎。他们实现的成员函数之一是void discard(int long long z)
,它跳过z随机生成的数字。此功能的复杂性在www.cplusplus.com(http://www.cplusplus.com/reference/random/mersenne_twister_engine/discard/)
但是,在www.cppreference.com(http://en.cppreference.com/w/cpp/numeric/random/mersenne_twister_engine/discard)上有一条说明
对于某些引擎,已知“快速跳跃”算法,这种算法正在推进 没有计算的许多步骤(数百万的顺序)的状态 中间状态转换。
我如何知道哪些引擎丢弃的实际成本是O(1)?
答案 0 :(得分:3)
好吧,如果你使用预先计算的跳跃点,O(1)将适用于现有的每一个RNG。请记住,有一些算法可能比O(z)好,但不是O(1) - 比如说O(log 2 z)。
如果我们谈论跳到任意点,事情会变得有趣。例如,对于linear congruential generator,已知O(log 2 z)跳跃算法,基于F. Brown的论文,"随机数生成随机数,&# 34;跨。上午。核酸研究SOC。 (1994年11月)。代码示例为here。
C ++ 11标准中有LCG RNG,不确定在特定实现中前进的速度有多快(http://en.cppreference.com/w/cpp/numeric/random/linear_congruential_engine)
PCG family RNG共享相同的财产,我相信
答案 1 :(得分:3)
事实是,std::linear_congruential_engine<UIntType,a,c,m>::discard(unsigned long long z)
肯定可以非常有效地实施。它大部分等效于a
乘幂z
的幂m
(对于零和非零c
而言)-这意味着大多数基本软件实现都在O(log(z % phi(m)))
UIntType
乘法(素数为phi(m)=m-1
,一般为<m
),可以通过并行硬件求幂算法实现更快的执行速度。
^^^注意,因为O(log(z % phi(m)))
在某种程度上O(1)
是log2(z % phi(m)) < log2(m) < sizeof(UIntType)*CHAR_BIT
-尽管实际上它更像O(log(z))
。
对于其他大多数引擎的discard
函数,可能也有满足O(P(size of state))
约束(P(x)
-一些低阶多项式函数,最有可能是1+epsilon
阶的高效算法甚至更小的x*log(x)
,因为log(z) < sizeof(unsigned long long)*CHAR_BIT
可以被视为常量)。
出于某种未知原因 C ++标准(自ISO / IEC 14882:2017开始)不需要以比仅以更有效的方式实现discard
z
operator()()
调用任何PRNG引擎,包括绝对允许使用此引擎的。
就我个人而言,这令人困惑,毫无意义。这直接与先前的C ++原则矛盾,后者仅就性能而言仅对合理的功能进行标准化-例如尽管使用std::list<T>::operator[](size_type n)
迭代器operator++()
n
次很容易,但否 begin()
很自然,因为{{1 }}执行时间会使该函数在实践中成为不合理的选择。由于这个显而易见的原因,O(n)
和a[n]
不是强制性序列容器要求的一部分,而是可选序列容器操作的一部分。为什么在世界范围内,a.at(n)
是强制性随机数引擎要求的一部分,而不是某些具有足够复杂性要求的可选操作部分条目,例如e.discard(z)
或O(size of state)
? / p>
更令人困惑的是在我的GCC中实际找到了这个现实世界的实现:
O(P(size of state))
再一次,我们除了自己实现所需的功能外别无选择...
答案 2 :(得分:0)
我认为这些事情根本不存在。我的启发式结论是O(1)
跳转RNG本质上是一个哈希,具有所有这暗示的含义(例如,它可能根本不是“好” RNG)。
但是,即使您询问的是O(log z)
,我也看不到STL中实现了这一点。我在GCC中discard
能够使用的所有grep
函数都是简单的循环。
discard(unsigned long long __z)
{
for (; __z != 0ULL; --__z)
(*this)();
}
这不仅令人难过,而且还会引起误解,因为discard
仅在有有效方法的情况下才存在。
唯一不琐碎的是mersenne
(如下),但仍然是O(z)
。
discard(unsigned long long __z)
{
while (__z > state_size - _M_p)
{
__z -= state_size - _M_p;
_M_gen_rand();
}
_M_p += __z;
}
Boost的Mersenne具有跳过功能,但仅在大于10000000(默认值)的跳过时才被调用。这已经告诉我,跳过在计算上非常繁琐(即使它是O(log z)
)。
https://www.boost.org/doc/libs/1_72_0/boost/random/mersenne_twister.hpp
最后,对于线性同余,Trust显然具有有效的discard
,但仅在c=0
的情况下有效。 (我不确定这是否会使它的用处不大。)
https://thrust.github.io/doc/classthrust_1_1random_1_1linear__congruential__engine_aec05b19d2a85d02f1ff437791ea4dd68.html#aec05b19d2a85d02f1ff437791ea4dd68