编程实现:从n个数中无重复取m个数
首先,要满足要求,则在n个数中取到第i个数的概率须为: m n \frac{m}{n} nm
原因:第1次取到的概率: 1 n \frac{1}{n} n1
第二次取到的概率(第一次没取到): n − 1 n ∗ 1 n − 1 = 1 n \frac{n-1}{n} * \frac{1}{n-1}=\frac{1}{n} nn−1∗n−11=n1
则直到第m次,每次取到的概率均为 1 n \frac{1}{n} n1
则加起来和为 m n \frac{m}{n} nm
基于该前提,可先写出如下代码:
void getM(int n, int m)
{
for (int i = 0; i < n; ++i)
{
if (rand() % (n - i) < m)
{
cout << i << endl;
m--;
}
}
}
解析:
i表示取到数字i,则对于i=0,产生0~(n-1)的数,其在[0, m-1]的概率为
m
n
\frac{m}{n}
nm
对于i=1,产生0~(n-2)的数字, 此时有两种情况:
-
上一个i=0被取到,此时m=m-1,则i=1被取到的概率为产生的数在[0, m-2]的概率: m n ∗ m − 1 n − 1 \frac{m}{n}*\frac{m-1}{n-1} nm∗n−1m−1
-
上一个i=1未被取到,此时m为改变,则i=1被取到的概率为产生的数在[0, m-1]的概率: n − m n ∗ m n − 1 \frac{n-m}{n}*\frac{m}{n-1} nn−m∗n−1m
两者相加: m n ∗ m − 1 n − 1 + n − m n ∗ m n − 1 = m n \frac{m}{n}*\frac{m-1}{n-1} + \frac{n-m}{n}*\frac{m}{n-1}=\frac{m}{n} nm∗n−1m−1+nn−m∗n−1m=nm,与i=0被取到的概率相同
因此对于后面的所有i,均能以等概率 m n \frac{m}{n} nm被取到
扩展:
从文件中取出一行,要求每行被选中的概率相等,提前不知道行数n的大小
思路:遍历文件的每行,每次以 1 i \frac{1}{i} i1的概率替换被选择的行
也就是对于第1行,必被选择,则第2行,有 1 2 \frac{1}{2} 21的概率替换,使得最后选择第二行
那么假设选择的是第k行,对于第k+1~n行,均未被替换,则概率为
1
k
+
k
k
+
1
+
k
+
1
k
+
2
+
⋅
⋅
⋅
+
n
−
1
n
=
1
n
\frac{1}{k} + \frac{k}{k + 1} +\frac{k + 1}{k + 2} + ··· + \frac{n-1}{n}=\frac{1}{n}
k1+k+1k+k+2k+1+⋅⋅⋅+nn−1=n1
也就是每行被选中的概率为
1
n
\frac{1}{n}
n1