使用给定的随机数生成器打印1-100的最有效算法


11

我们给了一个随机数生成器RandNum50,该生成器均匀地生成1–50范围内的随机整数。我们可能仅使用此随机数生成器来生成并以随机顺序打印从1到100的所有整数。每个数字必须恰好出现一次,并且在任何地方出现任何数字的概率必须相等。

最有效的算法是什么?


1
使用数组/位向量记录已看到的数字,并使用计数器记录已看到的唯一数字的数量。
戴夫·克拉克

@DaveClarke我怎样才能生成一个大于50的数字?如果我使用了1次以上,那么如何使用它们生成1次呢?
Raj Wadhwa

1
当然,挑战在于确保所有地点均等地出现。您可以使用 RandNum100 = (RandNum50() * 2) - (RandNum50 > 25) ? 0 : 1)
戴夫·克拉克

2
@DaveClarke:所以您建议进行迭代拒绝采样?那只会在期望中终止。
拉斐尔

我只是在暗示。
戴夫·克拉克

Answers:


3

我以为(这个使用Fisher-Yates shuffle的解决方案是(所以可能是错误的。为了在每次迭代时均以良好的近似值保持均匀分布(请参见下面的“编辑”部分),可以使用此技巧来产生介于和之间的值:0 k 1O(N2)krand0k1

 // 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上面的函数不会返回真正的均匀分布。还有其他方法可以用来获得更好(任意更好)和更快的近似值。但是(据我所知)获得真正均匀分布的唯一方法是使用拒绝采样:选择随机位,如果获得的数量小于返回它,否则产生另一个随机数;可能的实现:ř ķm=log2(k)rk

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
    }
}

1
您链接到的Wikipedia页面指出存在变体。O(n)
戴夫·克拉克2012年

1
我认为“洗牌”是这里的关键词。
拉斐尔

4
krand(k)中的技巧虽然产生了很好的近似值,但并没有产生真正的均匀分布:即使对于k = 3,这也有33.333328%的机会输出0。 ?如果我们只是想要一个近似值,我认为较小的限制就足够了。
Erick Wong

1
@ErickWong:你是对的;我认为,只能使用不能保证以恒定时间结束的剔除采样方法才能达到真正的均匀分布。还有其他近似方案(允许达到任何所需的近似值),我提出的方案是我想到的第一个方案。
2012年

2
@ ex0du5:我知道,但是如何仅使用[1..100]中的统一随机生成器来产生数字[1..100]的统一随机排列?我知道的唯一替代方法是:step1)在选择一个随机值;步骤2)如果已经选择了则将其丢弃并转到步骤1;step3)打印 ; 步骤4)如果尚未打印所有100个数字,请转到步骤1。但是此方法只是将拒绝转移到已经选择的元素上。1..100 r rr1..100rr
2012年

4

既然其他人已经给出了近似的解决方案以及涉及不确定数目的偏差的解决方案,那么如何证明没有这样的算法可以保证只需要有限的RandNum50()呼叫次数呢?

正如其他人指出的那样,以随机顺序打印1-100之间的数字等效于打印这些数字的随机排列。有一百个!这些排列中的任何一个,因此必须以概率输出任何特定的排列。1100!

但是,如果我们知道我们的算法最多使用调用约,则可以得出如下结论:首先,填充那些执行少于调用的计算路径,以进行其他虚拟调用(即,返回值无关紧要),因此所有计算路径都精确地进行了调用。调用的任何给定序列必须导致一些输出排列,因此我们可以构建一个“结果表”,将“ 调用”的任何给定序列映射到特定的输出排列。由于这些结果均具有相同的可能性(每个结果都有概率k k k k r 1r 2r k1kRandNum50kkRandNum50kkRandNum50(r1,r2,,rk) Ç150k),那么对于某些,从我们的算法中获取任何特定排列的概率必须采用的形式。但是不能采用这种形式,因为不能将除以任何(例如3可以除以但是不能除以形式的任何数字)。这意味着将结果分配到随机数调用的可能性不可能达到统一的排列。 c1c50kc10050ķķ10050k1100!100!50kk100!50k


2

先前的解决方案不是最佳的。在调用RandNum50时,复杂度恰好是,并在进行了详细描述,用作随机位的来源(如Vor所建议):nlogn+O(1)

if ( rand50() > 25 ) then b = 1 else b = 0   // random bit

基本思想是,如果生成到之间的均值,则可以节省很多位,然后用阶乘碱分解,而不是产生制服的序列范围高达,然后,然后等,。正如我在帖子中提到的,这实际上是我提交的论文的主题!n 1 2 3 n1n!123n

如果您不知道如何从该随机位生成制服,如该文章中所建议,您还可以通过这种方式直接生成一个制服的近似值(相当于Vor的“ trulyrand”,但速度更快):

P = (RandNum50()-1) + (RandNum50()-1)*50^1 + (RandNum50()-1)*50^2 + ...

尽你所需要。这是开发在基座。然后简单地截断,即,在您的情况下。该值不是完全随机的,但它是经常使用的一致性度量。或者,按照Vor的建议,如果,则可以拒绝。然后使用此值,可以按照post中的描述进行阶乘基本扩展。50 PP50PQ=Pmodnn=100!P>n


1

我没有做过分析,确认如何统一(或没有),这将是,它可以调整到一个真正的洗牌,但你可以只选择,从的起始阵列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。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.