随机抽样有关问题(蓄水池有关问题)
本文整理了一些博客的思路:
http://www.cnblogs.com/HappyAngel/archive/2011/02/07/1949762.html
http://blog.****.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个元素之一,此时的概率为:
/**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