等概率随机采样问题
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