指点成金-最美分享吧

登录

蓄水池抽样算法

佚名 举报

篇首语:本文由小编为大家整理,主要介绍了蓄水池抽样算法相关的知识,希望对你有一定的参考价值。

最近面试字节,被面试官问到一个这样的问题:给定一个超大的文件,从该文件中随机选取k行文本出来,使得每一行被选择的概率相等。当时一脸懵,后来了解到这个问题需要通过蓄水池抽样算法来解决,经过两天的学习大概搞明白了算法原理,简单记录一下。

1.k=1的情况

k=1时,也就是从文件中选取一行文本,假设总共有n行文本,则必须保证每一行被选择的概率为 1 / n 1/n 1/n。当我们可以获取所有文本时我们可以很容易利用rand()%n来等概率获取一行文本。但是在很多情况下,我们无法提前预知n的值或者代价较大。比如一个超大文件,无法放入内存,如果想知道行数,我们只能先分块读取整个文件获得行数,然后随机选定一行再重新读一遍文件,这就会导致两次文件读取。又比如数据是流式数据,此时我们根本无法获得n的值。我们希望能有一个算法使得我们在流式读取数据的过程中一遍就可以等概率获得想要的数据,蓄水池算法就是为此而生的。
其基本流程如下:从第一行开始遍历整个文本,当遍历到第i行时,随机选择区间 [0,i)内的一个整数,如果其等于 0,则将答案置为当前行文本,否则答案不变。该算法会保证每一行成为最后被返回的行的概率均为 1 n \frac 1n n1,证明如下:
p ( 第 i 行 为 最 后 被 返 回 行 的 概 率 ) p(第i行为最后被返回行的概率) p(i)
= p ( 第 i 次 随 机 选 择 的 值 为 0 ) ∗ p ( 第 i + 1 次 随 机 选 择 的 值 不 为 0 ) ∗ . . . ∗ p ( 第 n 次 随 机 选 择 的 值 不 为 0 ) =p(第i次随机选择的值为0)*p(第i+1次随机选择的值不为0)*...*p(第n次随机选择的值不为0) =p(i0)p(i+10)...p(n0)
= 1 i ∗ ( 1 − 1 i + 1 ) ∗ . . . ∗ ( 1 − 1 n ) =\frac 1i*(1-\frac1i+1)*...*(1-\frac1n) =i1(1i+11)...(1n1)
= 1 i ∗ i i + 1 ∗ . . . ∗ n − 1 n =\frac 1i*\frac ii+1*...*\frac n-1n =i1i+1i...nn1
= 1 n =\frac1n =n1

这就证明了按照上述流程获得的文本即为从n行文本中等概率获得的一行文本。同时我们也会发现,按照上述流程我们只需要一次遍历整个文件即可。

2.k>1的情况

当k>1时,也即我们需要从整个文件中等概率选择多行出来。基本思路和一行类似,但是会更为复杂。先描述一下基本操作流程:
对前k行文本,我们需要全部保留;对于第i(i>k)行文本,我们以 k i \frac ki ik的概率保留,等价描述为随机选择区间 [ 0 , i ) [0,i) [0,i)内的一个数,如果小于k则保留;保留下来之后,我们用第i行将之前已保留的k行文本中随机选择的一行进行替换;同样的操作施加到后续的第i+1行直到第n行。按照上述流程选取的k行文本满足每一行被选择的概率为 k n \frackn nk,证明如下:

  • 对第i行来说,它被选择的概率为 k i \frac ki ik,无需证明;
  • 对第j(0 p ( j 行 保 留 ) = p ( j 行 上 一 轮 被 保 留 ) ∗ p ( i 行 丢 弃 ) + p ( j 行 上 一 轮 被 保 留 ) ∗ p ( i 行 保 留 ) ∗ p ( j 行 未 被 替 换 ) p(j行保留)=p(j行上一轮被保留)*p(i行丢弃)+p(j行上一轮被保留)*p(i行保留)*p(j行未被替换) p(j)=p(j)p(i)+p(j)p(i)p(j)
           = k i − 1 ∗ i − k i + k i − 1 ∗ k i ∗ k − 1 k \space\space\space\space\space\space=\fracki-1*\fraci-ki+\fracki-1*\fracki*\frack-1k       =i1kiik+i1kikkk1
           = k i \space\space\space\space\space\space=\frac ki       =ik

从而证明了前i行每一行被选择的概率为 k i \frac ki ik,从而得证。
此外,当我们证明了k>1的情况再套回k=1的情况时会发现,k=1的情况其实是k>1的特例,只不过在k=1时必定会和前面已选择的一行进行交换而已。

以上是关于蓄水池抽样算法的主要内容,如果未能解决你的问题,请参考以下文章