假设我们现在要在集合 { 0 , 1 , 2 , ⋯ , n − 1 } \{0,1,2,\cdots,n-1\} {
0,1,2,⋯,n−1}中随机抽取 k k k个数( k ≤ n k\le n k≤n)。显然每个元素被抽中的概率均为 k n \frac{k}{n} nk。C++
代码如下:
vector<int> sample_integers(int n, int k = 3)
// 在{0, 1, 2, ..., n - 1}中等可能地抽取k个元素
{
vector<int> result;
int i = 0;
for(; n > 0; ++i, --n)
{
if(gen() % n < k) // 这个i被选中的几率是k/n
{
--k;
result.push_back(i);
}
}
return result;
}
时间复杂度为 O ( n ) O(n) O(n)。要理解这种算法的正确性,我们只需证明每个元素被抽中的几率都是 k n \frac{k}{n} nk。又因为数学归纳法,我们只需证明第一个元素被抽中的概率等于后面的元素被抽中的概率。
显然,抽中第一个元素 0 0 0的概率是 k n \frac{k}{n} nk。如果第一个元素被抽中了,那么后面的每个元素被抽中的概率是 k − 1 n − 1 \frac{k-1}{n-1} n−1k−1;如果第一个元素没有被抽中,后面的每个元素被抽中的概率是 k n − 1 \frac{k}{n-1} n−1k。根据全概率公式,后面的每个元素被抽中的概率是 P { 后面的某个被抽中抽 } = P { 后面的某个被抽中抽 ∣ 第一个元素被抽中 } P { 第一个元素被抽中 } + P { 后面的某个被抽中抽 ∣ 第一个元素没有被抽中 } P { 第一个元素没有被抽中 } = k − 1 n − 1 k n + k n − 1 n − k k = k n − 1 k − 1 n + k n − 1 n − k n = k n − 1 ( k − 1 n + n − k n ) = k n − 1 n − 1 n = k n \begin{aligned} P\{\text{后面的某个被抽中抽}\}&=P\{\text{后面的某个被抽中抽}|\text{第一个元素被抽中}\}P\{\text{第一个元素被抽中}\}\\&\qquad+P\{\text{后面的某个被抽中抽}|\text{第一个元素没有被抽中}\}P\{\text{第一个元素没有被抽中}\}\\ &=\frac{k-1}{n-1}\frac{k}{n}+\frac{k}{n-1}\frac{n-k}{k}\\ &=\frac{k}{n-1}\frac{k-1}{n}+\frac{k}{n-1}\frac{n-k}{n}\\ &=\frac{k}{n-1}\left(\frac{k-1}{n}+\frac{n-k}{n}\right)\\ &=\frac{k}{n-1}\frac{n-1}{n}\\ &=\frac{k}{n} \end{aligned} P{ 后面的某个被抽中抽}=P{ 后面的某个被抽中抽∣第一个元素被抽中}P{ 第一个元素被抽中}+P{ 后面的某个被抽中抽∣第一个元素没有被抽中}P{ 第一个元素没有被抽中}=n−1k−1nk+n−1kkn−k=n−1knk−1+n−1knn−k=n−1k(nk−1+nn−k)=n−1knn−1=nk这样就证明了抽中每个元素的概率均为 k n \frac{k}{n} nk。