一道关于随机数生成的题目

最近看到一个阿里的面试题目,觉得很有意思,遂记下。

  阿里2014年笔试题目,(选择题)是给定生成1-7的随即函数rand_7,看是否能生成其它随机数? 

  A. rand_3     B. rand_21  C. rand_23  D.rand_47

首先从最简单的开始,给定一个能够随机生成1到7之间数字的rand_7方法,能否用其实现rand_3?

非常直观的方法就是直接使用rand_7生成数字,如果为1 ~ 3则返回即可,代码如下:

 1 int rand_3()
 2 {
 3  int x;
 4  while(x = rand_7())
 5  {
 6      if(x <= 3)
 7      {
 8          return x;
 9      }
10  }  
11 }  

需要证明该方法能够以1/ 3概率生成1 , 2, 3 证明过程如下:

1. rand_7()以1/7概率生成1~7;

2. 循环中第一次生成1并返回的概率为1/7;

3. 循环中第二次生成1并返回的概率应该为 (4 / 7) * (1 * 7) (第一次循环没有退出的概率 + 本次生成1的概率);

4. 同理,之后的每一次生成1的概率均为前几次没有退出(生成4, 5, 6, 7)的概率加上本次生成1的概率 有 P(n)= (4 / 7) ^ (n - 1)  * (1 / 7);  (n >= 1)

5. 这样,rand_3()生成1的概率为 P  = 1/ 7 +  (4 / 7) * (1 * 7) + (4 / 7) ^ 2  * (1 / 7) + (4 / 7) ^ 3  * (1 / 7) + .... + (4 / 7) ^ (n - 1)  * (1 / 7); ( n -> 无穷)

6. 以上求和公式求得P = 1 / 3; (该等比数列收敛)

同理,生成2 ,3 的概率也为1/3,说明该方法是可行的。

这里其实还可以优化一下,减少while循环命中失败率(以下还有说明)。

 1 int rand_3()
 2 {
 3     int x;
 4     while(x = rand_7())
 5     {
 6         if(x <= 6)
 7         {
 8             return x % 3 + 1;
 9         }
10     }
11 }

 

同时引申出一个通用的结论, 如果 b > a, 我们可以使用能够等概率生成1~b之间数字的rand_b() 去实现rand_a(). 而且rand_a()等概率生成1 ~ a之间的数字.

伪代码如下:

 1 // a < b
 2 int rand_a()
 3 {
 4  int x;
 5  while(x = rand_b())
 6  {
 7      if(x <= a)
 8      {
 9          return x;
10      }
11  }  
12 }  

那如果a > b呢? 也就是说能不能使用一个生成范围小的随机数方法去实现一个范围大的随机数呢,也就是说还能不能用rand_b()去实现rand_a()?

 既然这样我们可以用rand_b()的组合去实现rand_a()对吗?(a > b),但是问题就是如何保证等概率生成1 ~ a

例如: rand_5() + rand_5() - 1

  能够生成1~9的数字,可是并不能等概率生成1~9,例如生成1的组合只有(1, 1),生成2的组合有(1, 2)和(2, 1),生成3的有(1, 3),(3, 1),(2, 2). 等,因此仅仅简单组合是不能保证等概率的。那怎么找到一个等概率生成数的组合呢?

首先,再来看一个组合,然后再进行分析。组合如下:

  5 * (rand_5() - 1) + rand_5()

加号前面部分等概率生成0, 5, 10, 15, 20, 后面部分等概率生成1 ~ 5,这样1 ~ 25的每一个数字仅能通过一种组合实现,而且每一个数字的生成概率均为 1/5 * 1/ 5 = 1/ 25。

这样,相当于我们实现了rand_25(), 我们可以利用这个方法去实现rand_7(), 套用以上的代码:

 1 int rand_7()
 2 {
 3     int x;
 4     while(x = 5 *(rand_5() - 1) + rand_5())
 5     {
 6         if(x <= 7)
 7         {
 8             return x;
 9         }
10     }
11 }

目前看来使用rand_5()实现了rand_7()方法,但是以上的while循环次数过多,因为rand_5()组合生成1 ~ 25,只有1~7命中才能退出,我们应该尽量增大命中范围,减小舍弃数目的个数。根据分析,if语句判断的阈值越接近25越好,算法命中率就增加。我们可以这样(if语句中的阈值为接近25并小于25的7的倍数):

 1 int rand_7()
 2 {
 3     int x;
 4     while(x = 5 *(rand_5() - 1) + rand_5())
 5     {
 6         if(x <= 21)
 7         {
 8             return x % 7 + 1;
 9         }
10     }
11 }

同样我们可以证明生成1的概率是P = 3 / 25 + ( 4 / 25 ) * (3 / 25) + (4 / 25)^2 * (3 / 25) + ... = 1 / 7

好了,重点来了,我们将整个随机数生成问题泛化一下:

  有两个随机数生成方法rand_a() 和rand_b(),其中a,b不相等,能够使用rand_a()去实现rand_b() ?

1. 如果a > b,进入步骤2;否则构造rand_a^2 = a * (rand_a - 1) + rand_a, 表示生成1到a^2随机数的函数。如果a^2 仍小于b,继教构造 rand_a^3 = a * (rand_a^2 - 1) + rand_a 直到a^k > b,这时我们得到rand_a^k, 我们记为rand_A。

2.步骤1中得到rand_A()和rand_b() (A > b),我们就可用以下的代码去构造rand_b()

 1 int rand_b()
 2 {
 3     int x;
 4     while(x = rand_A())
 5     {
 6         if(x <= b * (A / b))   //  b*(A/b)表示最接近A且小于A的b的倍数
 7         {
 8             return x % b + 1;
 9         }
10     }
11 }

大功告成,无论a和b的大小关系如何(如何相等就不需要转化了对吧),我们总能用其中一个随机数生成方法去实现另一个随机数生成方法。

 

回过头,我们来看阿里巴巴的这道笔试题,有rand_7(),问是否能够生成其他的随机数。显而易见,所有答案选项均符合,因为我们无论如何都能构造出一个随机数生成器来,大于7或者小于7.

 

posted @ 2015-03-04 11:51  菜鸟加贝的爬升  阅读(400)  评论(0编辑  收藏  举报