水池抽样算法

最近从朋友那里听到了一个算法——水池抽样算法,了解了一下,觉得挺简单有趣的,于是就花了点时间学习了一下。

背景

从总数据中随机抽取k个数,要求每个数被抽到的概率相等。

引入

首先我们来看两道简单概率题,

  • 1,袋中放有3个球,红绿蓝,第一次取出一球,不放回,第二次再取一球.则抽到红球的概率是多少?
    答案: 1-2/3 * 1/2= 2/3
  • 2,袋中放有3个球,红绿蓝,第一次取出一球,放回,第二次再取一球.则至少抽到红球的概率是多少?
    答案: 1-2/3*2/3 = 5/9(取到一次红球4/9,两次红球1/9,两种情况相加).
  • 3,从 赤橙黄绿青蓝紫七个球中,取出3个球,取到赤球的概率是多少?
    答案: 3/7 (第一个就取到 1/7 + 第二个才取到 6/7 * 1/6 + 第三个才取到 6/7 * 5/6 * 1/5 = 3/7)

如果这些概率题能够理解,那么后面水塘抽样的理解就很简单了。

在什么情况下使用?

其实如果知道总数 n,我们直接产生n 内的k个随机数即可,因此用不到水池抽样。
水塘抽样的条件是在,不知道总数量(假设为n)的情况下,通过一次遍历,随机抽出k个数,这个随机指的是每个数被抽到的最终概率为(k/n),遍历的同时进行取样。
例如,要在一个链表中,通过一次遍历,随机取两个数。
或者,在一个极大文本中,随机抽取10行。等等。

思路和代码

思路:首先取1-k个数放进集合 result,然后从k+1开始遍历,在遍历的时候,取k+1内的一个随机数(假设为j),如果取到的数j <=k ,那么将这个遍历到的数替换进 result 的第j个数。 继续下一次遍历直到最后。(体现为代码值-1)。 代码很简单:

int arr[] = {1,2,3,4,5,6,7};  //假设值,实际应该是一个只可以判断是否到结尾的类集合对象。
int k=5;
int result[] =new int[k];
for(int i=0;i<k;i++){        //初始取值
	result[i]  = arr[i];
}
int x=k;
while(x<arr.length){   //实际的判断是,arr是否到最后了
	int j = new Random().nextInt(x);
	if(j<k){
		result[j] = arr[x];
	}
	x++;
}
//运行结束,最终result几位结果。
证明

明白了思路之后,代码很简单。抽样算法的一点难点只有证明部分。通过反向验证,其实也很简单,能够理解前面的概率题即可理解,如下:

  • 第1个数被选中,概率为: 1 * [k/(k+1) * (k+1)/(k+2) * ... * (n-1)/n ]= k/n (第一次被选中 * 后续遍历不被替换的概率)
  • 第x个数(x 大于k小于n,例如x=k+10)被选中,概率为: k/(k+10) * (k+10)/(k+11) * ... * (n-1)/n = k/n (在遍历第 k+10 时抽到的随机值为k内,即被选中,并且后续不被替换)
  • 第n个数被选中(最后)的概率:k/n (在遍历第 n 时抽到的随机值为k内,即被选中)

因此,可知,每一个数被选中的概率都为 k/n

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值