我们给了一个随机数生成器RandNum50
,该生成器均匀地生成1–50范围内的随机整数。我们可能仅使用此随机数生成器来生成并以随机顺序打印从1到100的所有整数。每个数字必须恰好出现一次,并且在任何地方出现任何数字的概率必须相等。
最有效的算法是什么?
RandNum100 = (RandNum50() * 2) - (RandNum50 > 25) ? 0 : 1)
。
我们给了一个随机数生成器RandNum50
,该生成器均匀地生成1–50范围内的随机整数。我们可能仅使用此随机数生成器来生成并以随机顺序打印从1到100的所有整数。每个数字必须恰好出现一次,并且在任何地方出现任何数字的概率必须相等。
最有效的算法是什么?
RandNum100 = (RandNum50() * 2) - (RandNum50 > 25) ? 0 : 1)
。
Answers:
我以为(这个使用Fisher-Yates shuffle的解决方案是(所以可能是错误的。为了在每次迭代时均以良好的近似值保持均匀分布(请参见下面的“编辑”部分),可以使用此技巧来产生介于和之间的值:0 k − 1krand
// return a random number in [0..k-1] with uniform distribution
// using a uniform random generator in [1..50]
funtion krand(k) {
sum = 0
for i = 1 to k do sum = sum + RandNum50() - 1
krand = sum mod k
}
Fisher-Yates算法变为:
arr : array[0..99]
for i = 0 to 99 do arr[i] = i+1; // store 1..100 in the array
for i = 99 downto 1 {
r = krand(i+1) // random value in [0..i]
exchange the values of arr[i] and arr[r]
}
for i = 0 to 99 do print arr[i]
正如Erick所指出的,krand
上面的函数不会返回真正的均匀分布。还有其他方法可以用来获得更好(任意更好)和更快的近似值。但是(据我所知)获得真正均匀分布的唯一方法是使用拒绝采样:选择随机位,如果获得的数量小于返回它,否则产生另一个随机数;可能的实现:ř ķ
function trulyrand(k) {
if (k <= 1) return 0
while (true) { // ... if you're really unlucky ...
m = ceil(log_2 (k) ) // calculate m such that k < 2^m
r = 0 // will hold the random value
while (m >= 0) { // ... will add m bits
if ( rand50() > 25 ) then b = 1 else b = 0 // random bit
r = r * 2 + b // shift and add the random bit
m = m - 1
}
if (r < k) then return r // we have 0<=r<2^m ; accept it, if r < k
}
}
既然其他人已经给出了近似的解决方案以及涉及不确定数目的偏差的解决方案,那么如何证明没有这样的算法可以保证只需要有限的RandNum50()
呼叫次数呢?
正如其他人指出的那样,以随机顺序打印1-100之间的数字等效于打印这些数字的随机排列。有一百个!这些排列中的任何一个,因此必须以概率输出任何特定的排列。
但是,如果我们知道我们的算法最多使用调用约,则可以得出如下结论:首先,填充那些执行少于调用的计算路径,以进行其他虚拟调用(即,返回值无关紧要),因此所有计算路径都精确地进行了调用。调用的任何给定序列必须导致一些输出排列,因此我们可以构建一个“结果表”,将“ 调用”的任何给定序列映射到特定的输出排列。由于这些结果均具有相同的可能性(每个结果都有概率k k k k (r 1,r 2,… ,r k)1RandNum50
RandNum50
RandNum50
Ç),那么对于某些,从我们的算法中获取任何特定排列的概率必须采用的形式。但是不能采用这种形式,因为不能将除以任何(例如3可以除以但是不能除以形式的任何数字)。这意味着将结果分配到随机数调用的可能性不可能达到统一的排列。 c1100!50ķķ100!50k
先前的解决方案不是最佳的。在调用RandNum50时,复杂度恰好是,并在此进行了详细描述,用作随机位的来源(如Vor所建议):
if ( rand50() > 25 ) then b = 1 else b = 0 // random bit
基本思想是,如果生成到之间的均值,则可以节省很多位,然后用阶乘碱分解,而不是产生制服的序列范围高达,然后,然后等,。正如我在帖子中提到的,这实际上是我提交的论文的主题!n !1 2 3 n
如果您不知道如何从该随机位生成制服,如该文章中所建议,您还可以通过这种方式直接生成一个制服的近似值(相当于Vor的“ trulyrand”,但速度更快):
P = (RandNum50()-1) + (RandNum50()-1)*50^1 + (RandNum50()-1)*50^2 + ...
尽你所需要。这是开发在基座。然后简单地截断,即,在您的情况下。该值不是完全随机的,但它是经常使用的一致性度量。或者,按照Vor的建议,如果,则可以拒绝。然后使用此值,可以按照post中的描述进行阶乘基本扩展。50 P
我没有做过分析,确认如何统一(或没有),这将是,它可以调整到一个真正的洗牌,但你可以只选择,从的起始阵列i
个指数= i + 1
的(k + RandNum50() + RandNum50() - 1) mod (100 - k)
指数,与去除,对于k
= 0..99?
这会“ RandNum50() + RandNum50()
向前推动” 分布中的峰值。
我很确定这不是我所说的那样正确,因为不能从首选中获得0索引(1),而且我无法快速看到产生0的替代1..50 + 1..50调整..99。
更新资料
为了解决我提到的问题,我有效地使用RandNum100
了问题注释中提到的方法来随机初始化第一个k
偏移量。
这会在前面产生明显的波浪状分布。
我没有用前进1的方法,而是先用另一种RandNum50
方法递增了k
。这产生的结果对我来说是足够随机的,但是仍然不是“真正”随机的,这很容易看出,如果将K更改为2。
在我照顾任何偶数K的地方测试VB.NET代码。请注意,实际上它是O(K),即6K + 2。