随机抽样问题(蓄水池问题)

     本文整理了一些博客的思路:

                 http://www.cnblogs.com/HappyAngel/archive/2011/02/07/1949762.html

                            http://blog.csdn.net/fanzitao/article/details/7930886

问题定义

      我们常常碰到如下几个问题:

  (1)在不知道文件总行数的情况下,如何从文件中随机的抽取一行?

   上面的问题,我们并不知道文件有多大,如果知道了,我们就可以利用传统的rand()来生成一个随机数,然后选取相应的记录出来就行了。那么如果不知道文件的大小的时候,我们需要一个概念来帮助我们做出猜想,来使得对每一行取出的概率相等,也即随机。这个概念即蓄水池抽样(Reservoir Sampling)

  (2)给定一个数据流,其中包含无穷尽的搜索关键字(比如,人们在谷歌搜索时不断输入的关键字)。如何才能从这个无穷尽的流中随机的选取1000个关键字?

  (3)给定一个未知长度的整数流,如何随机选取一个数;


蓄水池抽样问题


1.问题1和2的解答

  对于第(1)(2)个问题其实是一个问题。我们首先定义取出的行号为choice,第一次直接以第一行作为取出行 choice ,而后第二次以二分之一概率决定是否用第二行替换 choice ,第三次以三分之一的概率决定是否以第三行替换 choice ……,以此类推,可用伪代码描述如下:

i = 0
while more input lines
   with probability 1.0/++i //我们以(1/i)决定当前元素选不选
       choice = this input line
print choice
 
    问题:这种方法能保证n个元素的每个元素选取的概率为1/n,下面我们证明一下。

    证明:对于第i个元素被选中的条件,第i次选择的时候被选中了,同时从i+1到n的选择过程中,第i个元素一直没被替换。换句话说,i+1到n选择过程中,它们都没选中。

   (1)第i个元素被选中的概率:1/i,这里插一个第i个元素没被选中的概率(i-1)/i。

   (2)第i+1到n没有被选中的概率:

   (3)那么最后,第i个元素被选中的概率为多少呢?


   (4)得证。看不懂,可以参考:http://www.cnblogs.com/HappyAngel/archive/2011/02/07/1949762.html

/**copyright@lsj on juyuan*/
#include <iostream>
#include <cassert>
#include <time.h>

using namespace std;

int RandSelectNum(int arr[],int len)
{
	assert(arr && len>0);
    int randNum = arr[0];
	srand(unsigned(time(NULL)));
	for(int i=1; i<len; ++i){
		if(rand()%(i+1) == i)
			randNum = arr[i];
	}
	return randNum;
}

int main()
{
	const int SIZE = 7;
	int arr[SIZE] = {1,2,3,4,5,6,7};

	cout<<RandSelectNum(arr,SIZE)<<endl;

	system("pause");
	return 0;
}


2.问题3的解答

     类比前面(1)(2)的答案,我们对问题(3)可以给出如下解法:先申请一个k个元素的蓄水池(数组),首先把前k个数放入蓄水池。对第k+1,我们以k/(k+1)概率决定是否要把它换入蓄水池,换入时随机的选取一个作为替换项。这样一直做下去,对于任意的样本空间的第i个样本(i>=1000),对每个数的选取概率都为k/i。也就是说对每个数选取概率相等。下面给出伪代码:

Init : a reservoir with the size: k
for i= k+1 to N
    M=random(1, i);//当i>k时,从1到i选择一个随机数,选择的随机数小于k,将第i个元素交换到前面去
    if( M < k)
     SWAP the Mth value and ith value
end for 

注意:这里是从1到i(i>1000)生成一个随机数rand,如果这个数满足小于k,说明第i个元素可以第k个元素交换,即执行swap(arr[rand],arr[i])。如果rand>1000,则第i个元素不放入蓄水池,即不交换。


     问题:这种方法能保证每个元素选取的概率为k/N,其中k表示随机去k个元素,N表示随机取N个元素。

    证明:跟上面一样,我们还是这样分析。对于第i个元素被选中的条件,第i次选择的时候被选中了,同时从i+1到n的选择过程中,第i个元素一直没被替换。换句话说,i+1到n选择过程中,它们都没选中。

   (1)对于第i个元素第i次被选择的概率为:k/i,第i个元素第i次没被选择的概率为(i-k)/i。

   (2)i个元素在最后被选中还要保证一个条件:从i+1到n的选择中,第i个元素没有被交换出去。这里没被交换出去有两种情况,我么考虑从i+1到n过程中的一个元素j

           情况1:第i+1到n的选择中,选择交换了前k个元素,但是没有选到第i个元素。这里我们考虑j个元素(i+1<=j<=n),此时的概率:

                                                                    

           情况2:第i+1到n的选择中,根本没有选中前面的k个元素之一,此时的概率为:

     总结上两种情况,第j个元素选不中j的概率为:

    (3)这样,第i个元素被最后选中的概率就可以想了:第i个元素在第i池选择中被选择进入蓄水池,而从i+1到n过程中都没有选中i,把i从蓄水池中交换出去,这样i就一直留在蓄水池中。

    (4)此时,已全部得证。看不懂,参考:http://blog.csdn.net/fanzitao/article/details/7930886
/**copyright@lsj on juyuan*/
#include <iostream>
#include <cassert>
#include <time.h>

using namespace std;

void RandSelectKNum(int arr[],int len,int result[],int resLen)
{
	assert(arr && len>0 && result && resLen);
    
	srand(unsigned(time(NULL)));
	int i = 0;
	for(i=0; i<resLen; ++i){//放k个元素进入蓄水池
		result[i] = arr[i];
	}

	for(i=resLen; i<len; i++){
		int randNum  = rand()%(i+1);//产生随机数同蓄水池里的数交换
		if(randNum < resLen)
			result[randNum] = arr[i];
	}
}

int main()
{
	const int SIZE = 7;
	int arr[SIZE] = {1,2,3,4,5,6,7};
	const int SELECT = 3;
    int result[SELECT];

	RandSelectKNum(arr,SIZE,result,SELECT);
	for(int i=0; i<SELECT; ++i){
		cout<<result[i]<<" ";
	}
	cout<<endl;
	system("pause");
	return 0;
}

附一个概率相关题目的连接:http://www.cnblogs.com/waytofall/archive/2012/08/27/2658757.html



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值