等概率随机采样问题

1. 输入包含两个整数m和n,其中m<n。输出[0,n-1]内的m个随机数,要求:每个数选择出现的概率相等(也就是m/n),且按序输出。

  依次考虑整数0,1,2,...,n-1,并通过一个适当的随机测试对每个整数进行选择。通过按序访问整数,可以保证输出结果是有序的。 if m=2 and n=5,那么选择的每一个数字的概率都应该是2/5。

  分析过程:在0,1,2,3,4这五个数字中

  第一次遇到0时,它的选择概率应该是2/5,如果选中了,我们开始测试第二个数1,这个时候因为1选中了,所以1这个数字的选中概率就变小了,变成1/4了,有人说这似乎不对吧,因为题目说让每一个数字选中的概率是一样大的,而现在?一个2/5,一个1/4,这怎么行呢?其实不是这样的,认真思考一下就知道了,数字1选中的概率等于什么?

  数字1选中的概率p(1) = 数字0选中的概率 *(1/4)+数组0没选中的概率*(2/4)这样推算下 (2/5 * 1/4) + (3/5 * 2/4) = 8/20 = 2/5

  select = m, remaining = n

  for i in [0,n):

    if (rand() % remaining) < select:

      print i

      select --

    remianing--

 

  代码遵守的规则应该是要从r个剩余的整数中选出s个,我们以概率s/r选择下一个数。这个概率的选择方式和我们上面证明的是一样的。所以在程序结束的时候一定会打印出m个数字,且每一个数字的被选择概率相同,为m/n。 

  这个题目还有其他的解法,将问题定义为蓄水池抽样。先把前k个数放入蓄水池中,对第k+1,我们以k/(k+1)的概率决定是否要把它换入蓄水池,换入时我们可以随机挑选一个作为替换位置,这样一直到样本空间N遍历完,最后蓄水池中留下的就是采样结果。这样的方法得到的结果每一个数字被选择的概率也是k/n。

2. 问题扩展:如何从n个对象(可以以此看到这n个对象,但事先不知道n的值)中随机选择一个?比如在不知道一个文本中有多少行,在这样的情况下要求你随机选择文件中一行,且要求文件的每一行被选择的概率相同。 在知道n这个总对象个数的情况下,谁都知道概率是1/n. 但是我们现在不知道,怎么办呢?

  考虑这样是不是可以,总是以1/i的概率去选择每一次遍历的对象,比如从1,2,3,....,N, 每一次遍历到x时,总是以1/x的概率去选择它。

  总选择第一个行,并以概率1/2选择第二行,以1/3选择第三行,也就是说设结果为result,遍历第一个时result = 1,第二个以1/2的概率替让result = 2,这样一直遍历概率性的替换下去,最终的result就是你的结果。被选择的概率就是1/n。

  第x个数被选择的概率 = x被选择的概率 * (x+1没被选择的概率) * (x+2没有被选择的概率) *......*(N没有被选择的概率)  

  被选择的概率 = 1/2  * 2/3 * 3/4 * 4/5 .....* (n-1/n) 我想你知道答案了吧? 对! 是1/n.这样就可以在不知道N的大小的情况下等概率的去选择任意一个对象了!

参考伪代码如下:

i = 0

while more input lines:

  with prob 1.0/++i

  choice = this.line

print choice

3. 已知rand(),以p的概率产生0,以1-p的概率产生1,现在要求设计一个新随机函数newRand(), 使其以1/n的等概率产生1~n之间的任意一个数。
  可以通过已知随机函数rand()产生等概率产生0和1的新随机函数Rand(),然后调用k(k为整数n的二进制表示的位数)次Rand()函数,得到一个长度为k的01序列,以此序列所形成的整数即为1~n之间的数字。
  1):计算整数n的二进制表示所拥有的位数k,k = 1 +log2n(log以2为底n)

  2):调用K次Rand()产生随机数。注意:从产生序列得到的整数有可能大于n,如果大于n的话,则重新产生直至得到的整数不大于n

4. 给定一个函数rand5(),该函数可以随机生成1-5的整数,且概率一样。现要求使用该函数构造函数rand7(),使得可以随机等概率的生成1-7的整数。
  很多人的第一反应是利用rand5() + rand()%3来实现rand7()函数,这个方法确实可以产生1-7之间的随机数,但是数字生成的概率是不相等的。rand()%3 产生0的概率是1/5,而产生1和2的概率都是2/5,所以这个方法产生6和7的概率大于产生5的概率。
  正确的方法是利用rand5()函数生成1-25之间的数字,然后将其中的1-21映射成1-7,丢弃22-25。例如生成(1,1),(1,2),(1,3),则看成rand7()中的1,如果出现剩下的4种,则丢弃重新生成。

  基于,rand()产生[0,N-1],把rand()视为N进制的一位数产生器,那么可以使用rand()*N+rand()来产生2位的N进制数,以此类推,可以产生3位,4位,5位...的N进制数。这种按构造N进制数的方式生成的随机数,必定能保证随机,而相反,借助其他方式来使用rand()产生随机数(如 rand5() + rand()%3 )都是不能保证概率平均的。 

题目3:给定一个函数rand()能产生0到n-1之间的等概率随机数,问如何产生0到m-1之间等概率的随机数?

int random(int m , int n)
{
  int k = rand();
  int max = n-1;
  while(k < m)
  {
    k = k*n + rand(); 生成n进制数
    max = max*n + n-1; 生成n进制所能表示的刚好大于等于m的数
  }
  return k/(max/n); 此时k是小于m
}

5. 如何产生如下概率的随机数?0出1次,1出现2次,2出现3次,n-1出现n次?

int random(int size)
{
  while(true)
  {
    int m = rand(size);
    int n = rand(size);
    if(m + n < size)
      return m+n;
  }
}

 采用这种方法保证:

  1 只有 1

  2 可以有 1 1 / 0 2 / 2 0

  3 可以有 1 2 / 2 1 / 0 3 / 3 0

 

posted on 2015-06-09 12:59  keketse  阅读(1580)  评论(0编辑  收藏  举报

导航