水塘抽样算法(Reservoir Sampling Algorithm)

应用场景

主要用于解决大数据流中的随机抽样问题,即:当内存有限,数据长度很大,甚至未知,那么如何从中随机选取k个数据,并且要求是等概率。

算法步骤

水塘抽样的步骤是,只遍历一次,每次都考虑一个问题:当前元素是否被选中,选中后替换之前选中的哪一个元素。

采样过程:
step1:首先将前k个元素全部选取。
step2:对于第i个元素(i>k),以概率k/i来决定是否保留该元素,如果保留该元素的话,则随机丢弃掉原有的k个元素中的一个(即原来某个元素被丢掉的概率是1/k)。

算法原理

假设我们要遍历的数据规模为 n n n

(1)首先,我们来考虑前 k k k个元素:

 遍历前 k k k个元素时,我们以 p = 1 p=1 p=1的概率将该元素(或者与该元素相关联的信息,例如索引)加入result。当遍历到 k + 1 k+1 k+1个元素时,第 k + 1 k+1 k+1个元素被保留的概率是 k / ( k + 1 ) k/(k+1) k/(k+1),对于此时result中的元素,它们任何一个被剔除的概率是 1 / k 1/k 1/k,所以,对于前 k k k个元素,它们在这一次遍历中被剔除的概率为
k k + 1 × 1 k \frac{k}{{k + 1}} \times \frac{1}{k} k+1k×k1

相对应的,对于前 k k k个元素中的任一个,它们在第 k + 1 k+1 k+1次遍历中被保留的概率为
1 − k k + 1 × 1 k 1 - \frac{k}{{k + 1}} \times \frac{1}{k} 1k+1k×k1

所以,当遍历到第 n n n个元素时,前 k k k个元素中的任一个被保留的概率为
1 × ( 1 − k k + 1 × 1 k ) × ( 1 − k k + 2 × 1 k ) × ⋯ × ( 1 − k n − 1 × 1 k ) × ( 1 − k n × 1 k ) = k n 1 \times (1 - \frac{k}{{k + 1}} \times \frac{1}{k}) \times (1 - \frac{k}{{k + 2}} \times \frac{1}{k}) \times \cdots \times (1 - \frac{k}{{n - 1}} \times \frac{1}{k}) \times (1 - \frac{k}{n} \times \frac{1}{k})= \frac{k}{n} 1×(1k+1k×k1)×(1k+2k×k1)××(1n1k×k1)×(1nk×k1)=nk

(2)其次,我们来考虑前 k k k个元素之后的所有元素:

 对于第i个元素(i>k),以概率k/i来决定是否保留该元素。并且在之后的每一次遍历中,该元素被剔除的概率都为
k i + m × 1 k \frac{k}{{i+m}} \times \frac{1}{k} i+mk×k1
m m m表示该次遍历为第i个元素之后的第 m m m次遍历

所以,第i个元素(i>k)最终被保留的概率为
k i × ( 1 − k i + 1 × 1 k ) × ( 1 − k i + 2 × 1 k ) × ⋯ × ( 1 − k n − 1 × 1 k ) × ( 1 − k n × 1 k ) = k n \frac{k}{i} \times (1 - \frac{k}{{i + 1}} \times \frac{1}{k}) \times (1 - \frac{k}{{i + 2}} \times \frac{1}{k}) \times \cdots \times (1 - \frac{k}{{n - 1}} \times \frac{1}{k}) \times (1 - \frac{k}{n} \times \frac{1}{k})= \frac{k}{n} ik×(1i+1k×k1)×(1i+2k×k1)××(1n1k×k1)×(1nk×k1)=nk

 这种随机采样方法不需要事先知道问题的规模大小,当遍历到第m(m>k)个元素时,前m个元素中的任一个被保留的概率均为 k / m k/m k/m,当遍历到第n(n>k)个元素时,前n个元素中的任一个被保留的概率均为 k / n k/n k/n

 一般的随机抽样方法需要事先知道数据规模,再用随机数从n个元素中随机抽样k个。但当数据规模特别大且难以用现有的存储空间保存它时,我们无法事先知道数据规模,这时水塘抽样算法就很适合解决这种问题。

代码实现

// 从长度为n的nums数组中随机选取k个数(水塘抽样)
    public int[] Reservoir(int[] nums,int k){
        int[] res=new int[k];
        Random random=new Random();
        for(int i=0;i<nums.length;i++){
            //前k个数直接加入res
            if(i<k) res[i]=i;
            //对于第i个数,以k/i的概率让它留下来
            else if(random.nextInt(i+1)<k)
                //从现有res中随机选一个数踢出去,用留下来的数替换它
                res[random.nextInt(k)]=i;
        }
        return res;
    }
  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冷冰殇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值