如何等概率的从N个元素中选取出K个元素?
从1....n中随机输出m个不重复的数。(迅雷2011.10.21笔试题)
算法1
knuth(int n, int m)
{
for (int i=0; i<n; i++)
{
if ( rand_n()<m ) // rand_n()为生成[0,n)这个区间内的随机数
// rand_n()<m的概率就是m/n
{
cout<<i<<endl;
}
}
}
算法2
knuth(int n, int m)
{
srand((unsigned int)time(0));
for (int i=0; i<n; i++)
{
//假设还要从后面n-i个数中选m个元素
//那么每个元素被选中的概率为rand()%(n-i)<m
//因此每个元素的选中概率是等价的
if ( rand()%(n-i)<m )
{
cout<<i<<endl;
m--;
}
}
}
如何等概率的从数据流S中选取出K个元素?
由于数据量大小未知,因此无法使用上面的算法。我们能做的仅仅是保证所有已经输入的数据(假设已经有N个了)已k/N的等概率被选出,从而满足需求。算法如下:
S为元素数据流
初始化: 初始化一个大小为K的元素空间buf
for i =1 to k
buf[i] = S[i]
for i= k+1 to N
{
//第i个元素以 k/i 的概率留下
M=random(1, i);//生成一个1到i的随机数
if( M <= k)
buf[M]= S[i]
}
原理:
假设当前是i+1, 那么i+1这个元素被选中的概率是k/i+1。考虑前i个元素,如果前i个元素被选中的概率都是k/i+1,则算法满足要求。证明:
a. N<=k 时 元素全保留,留下来的概率 1.
归纳证明:k < i <=N
- 当i=k+1时
第k+1个元素被选中的概率为k/(k+1)。
从K个位置中随机选出一个位置放入新数据。有1/k的概率被选出。
需要该操作的概率是k/(k+1),因此有1/k * k/(k+1) = 1/(k+1)的概率被选出。
故k/(k+1)的概率保留在缓存中。即就是前面i个元素和第i+1个元素等概率的存在于缓存中,出现的概率为k/i。 - 假设当 j=i 的时候结论成立,此时以 k/i 的概率来选择第i个元素,前i-1个元素出现在蓄水池的概率都为k/i。
- 当j=i+1时:
元素j留下的概率为k/j = k/i+1;
从K个缓存中为元素j找一个位置放入K个元素有1/k的概率被替换
因此 本次操作 K个元素被替换的概率为(k/i+1) *(1/k) = 1/(i+1),
本次操作K个元素能保留下来的概率为 1 - 1/(i+1) = i/(i+1
)此前,我们已经从i个元素中等概率k/i的选了k个元素,
经过这次操作这K个元素有i/(i+1)保留
因此有k/i * i/(i+1) = k/(i+1)
综上所述,证明成立。
扰乱一个递增序列。 还未想明白
for i =[0,N)
swap(x[i],x[rand(i,n-1)];
有人证明,只要扰乱前m个就可以。
void sample_shuf(const int N,const int m)
{
int i, j;
int *x = new int[N];
for(i = 0 ; i <N ; i++) x[i]=i+1;
for(i = 0 ; i < m ; i ++)
{
j = rand(i,N-1);
swap(x[i],x[j]);
}
sort(x,x+m);
Print(x,m);
delete []x;
x= NULL;
}
关于采样的几个问题:
1、生成[1,5]整数的发生器,如何生成区间在[1,7]的发生器?解答:利用拒绝采样定理
首先,将(1,5)之间的随机发生器使用两次,按照五进制进行使用,拼成一个[0,24]的随即发生器既:([gen -1][gen -1])5,每一[]为一个5进制上的位,换算为十
进制为:x=(gen-1)*5+(gen-1) +1,在十进制上的范围为:1-25; 然后将(1,25)平均分配到7中情况上面,考虑21是7的倍数,因此可以每三个做一个映射(当然,也可以不管,直接截断7后面的数字,但是范围太小,效率不高),1-3--》1,4-6--》2,19-21--》7,此时就是等概率的,如果产生了22-25之间的数字,可以有两种方法决定结果:
(1)拒绝采样,重新再运算
(2)如果得到了22-25之间的数字,则此次的随即发生器结果,直接使用上一次得到的结果。这个方法有人证明过,是等概率的,算法Metropolis Algorithm。
2、Generate a random permutation for a deck of cards
解答:
从后往前,第k步的时候,随机产生一个1 到 k,之间的数字j,然后交换j和k处的数字,可以很容易的最后这个排列就是一个等概率得到的排列。
for k=N:1
j = rand(1,k)
swap(j,k)
end
同样的,也可以从前往后进行这个过程,不过产生的范围就是变成k-N之间了。
for k = 1:N
j = rand(k,N)
swap(j,k)
end
参考资料
- http://blog.sina.com.cn/s/blog_6344728a0100h6pz.html
- http://blog.youkuaiyun.com/v_july_v/article/details/6880698
- http://blog.youkuaiyun.com/hackbuteer1/article/details/7971328