蓄水池抽样

沉默了这么多天没有发过文章,怎么会突然看到这个算法的呢?

话说这是某搜索的二面面试官给的一个题目,我知道一定会有人吐槽,PS:提前说下,这道题在《编程珠玑》上有类似的。。。曾经这本书就在我面前,但是我没有珍惜,直到今天我才后悔莫及。。。。当时觉得这本书太乏味,没耐性看,事后一狠心就买了一套,一定要看.....哭。。。。。。

当时面试官是这样问的——给你一个文件,里面每一行包含一个字符串(海量的),请你随机返回100个字符串,必须每一个字符串返回的概率相同?

我们先来讨论下在不知道文件总行数的情况下,如何从文件中随机的抽取一行?

当时我一直纠结于说可以使用随机函数啊,就是那个rand(),面试官( ̄_, ̄ ),我想到的是我们做过类似的题目,不过是在知道文件行数的情况下,我们可以很容易的用C运行库的rand函数随机的获得一个行数,从而随机的取出一行,但是,当前的情况是不知道行数,这样如何求呢?

我们需要一个概念来帮助我们做出猜想,来使得对每一行取出的概率相等,也即随机。这个概念即蓄水池抽样(Reservoir Sampling)。

  我们便有了这样一个解决方案:定义取出的行号为choice,第一次直接以第一行作为取出行choice ,而后第二次以二分之一概率决定是否用第二行替换 choice ,第三次以三分之一的概率决定是否以第三行替换 choice ……,以此类推,可用伪代码描述如下:

i = 0
while more input lines
           with probability 1.0/++i
                   choice = this input line
print choice

  方法巧妙之处在于成功的构造出了一种方式使得最后可以证明对每一行的取出概率都为1/n(其中n为当前扫描到的文件行数),换句话说对每一行取出的概率均相等,也即完成了随机的选取。

  证明如下:

 

  如果扩展下的话那也就是我遇到的问题了  

如上面的思路,先把前100个字符串放入蓄水池,为了方面我这里将100定义为k,对第k+1,我们以k/(k+1)概率决定是否要把它换入蓄水池,换入时随机的选取一个作为替换项,这样一直做下去,对于任意的样本空间n,对每个字符串的选取概率都为k/n。也就是说对每个数选取概率相等。

Array[k];//定义一个k大小的字符串数组
for(int i = k+1; i < n; i++){
	N = rand(1,i);		//产生范围(0,i)行中随机的一行行号
	if(N < k)
		swap(N,i);		//第i行替换出第N行
}

  证明如下:

  

 面试后我疯狂的找答案,最后不禁为这个算法好奇,不过貌似在概率论与统计的课程中见到过,不难但是很不容易想到。。。

另外,在和网友的讨论中,我还得到了另外一种方法,即:每读取一行随机产生一个(0,1)的数,保存到一个数组,然后在第k+1开始,如果大于前K中的一个或多个数那么久置换出最小的那个,中间我们要保持前k个有序,可以只用数组,最好使用维护一个最大堆,这样我们最后留下最大的k个数对应的字符串即可.

但是,我个人感觉这种方法虽然很不错,但是如果考虑到海量和产生的随机数的精度问题,如果出现很多重复的数,这时候该怎么办呢??不知道谁有好的解释!请不吝赐教...

最后,不知道面试通过没,估计是挂了吧........哭哭哭...........

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值