排列
从{1,2,3,4,5} 5个元素中取3个数排列。第一次取有5种情况,拿出一个数后,第二次取有4种情况,第三次取有3中情况。所以5中取3的排列共有
5*(5-1)(5-2) = 60种情况,由{1,2,3} 3个数的排列由32*1=6中。也就是每一种组合有6种排列。
组合
所以{1,2,3,4,5}中取三个数的组合为
5
∗
(
5
−
1
)
∗
(
5
−
2
)
3
∗
(
3
−
1
)
(
3
−
2
)
=
10
\frac{5*(5-1)*(5-2)}{3*(3-1)(3-2)} = 10
3∗(3−1)(3−2)5∗(5−1)∗(5−2)=10
推广到一般情况,从N个元素中取K个元素的组合个数,记作C(N,K),则有
C
(
N
,
K
)
=
N
∗
(
N
−
1
)
∗
(
N
−
2
)
.
.
.
∗
(
N
−
(
K
−
2
)
)
∗
(
N
−
(
K
−
1
)
)
K
∗
(
K
−
1
)
∗
(
K
−
2
)
.
.
.
∗
3
∗
2
∗
1
C(N,K) = \frac{N*(N-1)*(N-2)...*(N-(K-2))*(N-(K-1))}{K*(K-1)*(K-2)...*3*2*1}
C(N,K)=K∗(K−1)∗(K−2)...∗3∗2∗1N∗(N−1)∗(N−2)...∗(N−(K−2))∗(N−(K−1))
其中,含有某个元素m的组合个数,取出m后,还剩N-1个元素,从中要抽取另外K-1个元素,和m组成组合。所以C(N,K)个组合中含有某个确定元素m的组合个数为C(N-1,K-1),那么对于N个元素取出K个元素,某个元素被抽中的概率
P
=
C
(
N
−
1
,
K
−
1
)
C
(
N
,
K
)
=
K
N
P = \frac{C(N-1,K-1)}{C(N,K)} = \frac{K}{N}
P=C(N,K)C(N−1,K−1)=NK
蓄水池抽样
1 | 2 | 3 | 4 | 5 | 6 | … | K | K+1 | … | N-1 | N |
---|
使用均匀随机数生成函数rand(),从N个元素中抽取K个元素。要求每个元素被抽中的概率符合
K
N
\frac{K}{N}
NK
为了便于理解,下面的说明违背了程序编写的传统,数组索引不从0开始,而从1起始。
从第K+1个元素开始处理,
第K+1个元素,int index = rand()%(K+1) + 1; index的取值范围是[1,K+1],如果 index 的取值在[1,K]上,则 把第K+1个元素与index位置的元素交换。这个事件发生的概率为 K K + 1 \frac{K}{K+1} K+1K
第K+2个元素,int index = rand()%(K+2) + 1; index的取值范围是[1,K+2],如果 index 的取值在[1,K]上,则 把第K+2个元素与index位置的元素交换。这个事件发生的概率为 K K + 2 \frac{K}{K+2} K+2K
第K+3个元素,int index = rand()%(K+3) + 1; index的取值范围是[1,K+3],如果 index 的取值在[1,K]上,则 把第K+3个元素与index位置的元素交换。这个事件发生的概率为 K K + 3 \frac{K}{K+3} K+3K
…
…
第N-1个元素,int index = rand()%(N-1) + 1; index的取值范围是[1,N-1],如果 index 的取值在[1,K]上,则 把第N-1个元素与index位置的元素交换。这个事件发生的概率为 K N − 1 \frac{K}{N-1} N−1K
第N个元素,int index = rand()%(N) + 1; index的取值范围是[1,N],如果 index 的取值在[1,K]上,则 把第N个元素与index位置的元素交换。这个事件发生的概率为 K N \frac{K}{N} NK
index | 1 | 2 | 3 | 4 | 5 | 6 | … | K | K+1 | … | N-1 | N |
---|---|---|---|---|---|---|---|---|---|---|---|---|
p | K K + 1 \frac{K}{K+1} K+1K | K N − 1 \frac{K}{N-1} N−1K | K N − 2 \frac{K}{N-2} N−2K |
证明,经过这轮操作后,N个元素中每个元素在前K个的概率均符合要求,分两种情况讨论。
1,索引为m的元素,m<=K,想要在处理后仍然在前K个,必须满足,所有的随机生成int index全不为m,淘汰是个单向过程,一旦被从前K个剔除,就无法回到前K个中。所以
P
=
K
K
+
1
∗
K
+
1
K
+
2
∗
K
+
2
K
+
3
∗
N
−
2
N
−
1
∗
N
−
1
N
=
K
N
P = \frac{K}{K+1} *\frac{K+1}{K+2} *\frac{K+2}{K+3} *\frac{N-2}{N-1} * \frac{N-1}{N} = \frac{K}{N}
P=K+1K∗K+2K+1∗K+3K+2∗N−1N−2∗NN−1=NK
2,索引为m的元素,m>K,在处理前不在前K个元素,所以它被抽中必须要满足处理到它时的计算出的index值小于等于K,这样m元素就被交换到了前K个,且在处理后面的元素是,m元素没被交换出去。
P
=
K
m
∗
m
m
+
1
∗
m
+
1
m
+
2
∗
N
−
2
N
−
1
∗
N
−
1
N
=
K
N
P = \frac{K}{m} *\frac{m}{m+1} *\frac{m+1}{m+2} *\frac{N-2}{N-1} * \frac{N-1}{N} = \frac{K}{N}
P=mK∗m+1m∗m+2m+1∗N−1N−2∗NN−1=NK
证的,N中某个任意元素经过这个操作,位置在K前个的概率等于,N个元素取出K个元素,某个元素被抽中的概率。
#include <vector>
#include <iostream>
using namespace std;
const int N = 1000;
const int K = 100;
int main(int argc, char *argv[])
{
vector<int> v;
for(int i=0;i<N;i++)
{
v.push_back(i);
}
for(int i=K;i<N;i++)
{
int index = rand()%(i+1);
if(index<K)
{
int temp = v[index];
v[index] = v[i];
v[i] = temp;
// cout<<index<<" "<< v[index] << " "<<v[i]<<endl;
}
}
for(int i=0;i<K;i++)
{
cout<<v[i]<<endl;
}
return 0;
}