水塘抽样(Reservoir Sampling)问题

      在高德纳的计算机程序设计艺术中,有如下问题:可否在一未知大小的集合中,随机取出一元素?。或者是Google面试题: I have a linked list of numbers of length N. N is very large and I don’t know in advance the exact value of N. How can I most efficiently write a function that will return k completely random numbers from the list(中文简化的意思就是:在不知道文件总行数的情况下,如何从文件中随机的抽取一行?)。两题的核心意思都是在总数不知道的情况下如何等概率地从中抽取一行?即是说如果最后发现文字档共有N行,则每一行被抽取的概率均为1/N?


  我们可以:定义取出的行号为choice,第一次直接以第一行作为取出行 choice ,而后第二次以二分之一概率决定是否用第二行替换 choice ,第三次以三分之一的概率决定是否以第三行替换 choice ……,以此类推。由上面的分析我们可以得出结论,在取第n个数据的时候,我们生成一个0到1的随机数p,如果p小于1/n,保留第n个数。大于1/n,继续保留前面的数。直到数据流结束,返回此数,算法结束。


  其实这个问题对应的思想就是水塘抽样,它是一系列的随机算法,其目的在于从包含n个项目的集合S中选取k个样本,其中n为一很大或未知的数量,尤其适用于不能把所有n个项目都存放到主内存的情况。(https://zh.wikipedia.org/wiki/水塘抽樣)


  这个问题的扩展就是:如何从未知或者很大样本空间随机地取k个数?亦即是说,如果档案共有N ≥ k行,则每一行被抽取的概率为k/N。


  根据上面(随机取出一元素)的分析,我们可以把上面的1/n变为k/n即可。思路为:在取第n个数据的时候,我们生成一个0到1的随机数p,如果p小于k/n,替换池中任意一个为第n个数。大于k/n,继续保留前面的数。直到数据流结束,返回此k个数。但是为了保证计算机计算分数额准确性,一般是生成一个0到n的随机数,跟k相比,道理是一样的。


算法描述(参见:https://en.wikipedia.org/wiki/Reservoir_sampling):

从S中抽取首k项放入「水塘」中
对于每一个S[j]项(j ≥ k):
   随机产生一个范围0到j的整数r
   若 r < k 则把水塘中的第r项换成S[j]项
/*
  S has items to sample, R will contain the result
*/
ReservoirSample(S[1..n], R[1..k])
  // fill the reservoir array
  for i = 1 to k
      R[i] := S[i]
 
  // replace elements with gradually decreasing probability
  for i = k+1 to n
    j := random(1, i)   // important: inclusive range
    if j <= k
        R[j] := S[i]

上述两个问题的证明可以参见:https://zh.wikipedia.org/wiki/水塘抽樣

参考文献:https://en.wikipedia.org/wiki/Reservoir_sampling,https://zh.wikipedia.org/wiki/水塘抽樣

转载自:过往记忆(http://www.iteblog.com/)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值