蓄水池问题描述:
假设有N个数,从中随机抽取K个数字,如果保证抽取的过程是等概率的?其中N是不固定的
类似问题:
1. 从100个数字抽取20个,如果向100个数字中再增加20个呢?
2. 给你一个长度为N的链表,N很大,但你不知道N有多大,你的任务是从这N个元素中取出K个元素,你只能遍历一次这个链表,算法保证取出的元素恰好有
K个,且他们是完全随机的.
3. 从一个不知长度的文件中随机取出K行
4. 从实时搜索的词中随机抽出K个词
由于N是无法在抽取是确定的,但是又要保证是随机的,所以此问题需要一个特定的算法,即是蓄水池算法
算法用代码表示如下:
int N,K;
void slove(int N,int K){
int *num = new int[N]; // num[0..K-1]存放蓄水池的元素,总共K个
for(int i = 0;i < N;i++)
num[i] = i+1;
for(int i = K;i < N;i++){
int p = rand()%K;
if(p < K)
swap(num[i],num[p]);
}
}
假设K = 20000,当有20001个元素时,第20001个元素被抽中的概率是20000/20001,那么前20000个元素被抽中的概率是多少呢?如果利用上面的蓄水池算法也是20000/20001,那么就符合我们的要求,下面用数学归纳法加以证明:
蓄水池中最多放入K个元素,设定当前元素为i,当i = K+1,K+2....,N时,
1. 当i = K+1是,第i个元素出现在蓄水池中的概率是K/K+1,前K个元素出现在蓄水池中的概率显然也是K/K+1
2. 假设当j = i 时,而且第i个元素出现在蓄水池中的概率是K/i,那么前i-1个元素出现在蓄水池中的概率也是K/i
3. 当j = i+1时,第i+1个元素出现在蓄水池中的概率明显是K/(i+1),现在证明前i个元素出现在蓄水池中的概率也是K/(i+1)
前i个元素出现在蓄水池中说明 ① 在第i+1次选择前,前i个元素已经在蓄水池中②在第i+1次选择时,前i个元素没有被替换掉
①由2知,前i个元素已经在蓄水池中的概率是是K/i
②先考虑被替换的因素,被替换说明:第一,第i+1个元素被抽中,概率是K/(i+1);第二,有一个蓄水池元素被抽中,概率是1/K
那么某个元素被替换的概率是K/(i+1) * 1/K = 1/(i+1),那么前i个元素在池中且没有替换掉的概率是1-1/(i+1) = i/(i+1)
综上所述,前i个元素出现在蓄水池中的概率是(K/i)*(i/(i+1))= K/(i+1).
参考文献:http://blog.youkuaiyun.com/linuxerhqt/article/details/6883785
陈利人 待字闺中