假设我们现在要在集合{0,1,2,⋯ ,n−1}\{0,1,2,\cdots,n-1\}{0,1,2,⋯,n−1}中随机抽取kkk个数(k≤nk\le nk≤n)。显然每个元素被抽中的概率均为kn\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)。要理解这种算法的正确性,我们只需证明每个元素被抽中的几率都是kn\frac{k}{n}nk。又因为数学归纳法,我们只需证明第一个元素被抽中的概率等于后面的元素被抽中的概率。
显然,抽中第一个元素000的概率是kn\frac{k}{n}nk。如果第一个元素被抽中了,那么后面的每个元素被抽中的概率是k−1n−1\frac{k-1}{n-1}n−1k−1;如果第一个元素没有被抽中,后面的每个元素被抽中的概率是kn−1\frac{k}{n-1}n−1k。根据全概率公式,后面的每个元素被抽中的概率是P{后面的某个被抽中抽}=P{后面的某个被抽中抽∣第一个元素被抽中}P{第一个元素被抽中}+P{后面的某个被抽中抽∣第一个元素没有被抽中}P{第一个元素没有被抽中}=k−1n−1kn+kn−1n−kk=kn−1k−1n+kn−1n−kn=kn−1(k−1n+n−kn)=kn−1n−1n=kn\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这样就证明了抽中每个元素的概率均为kn\frac{k}{n}nk。

博客围绕在集合{0,1,2,⋯,n−1}中随机抽取k个数(k≤n)的算法展开。给出了时间复杂度为O(n)的代码,通过数学归纳法和全概率公式,证明了该算法中每个元素被抽中的概率均为n/k。
106

被折叠的 条评论
为什么被折叠?



