蓄水池抽样算法(Reservoir Sampling)

1简介

        蓄水池抽样算法(Reservoir Sampling)是一种在数据流长度未知且内存有限的情况下,从数据流中随机抽取k个不重复样本的高效算法

        蓄水池抽样算法的核心在于它的效率和随机性。它通过一次遍历数据流来实现抽样,时间复杂度为O(N),空间复杂度为O(k)。这使得它非常适合处理大规模数据流或实时数据。蓄水池抽样算法不仅在理论上优雅,而且在实践中也非常有用。它在数据库抽样、数据流挖掘、机器学习的数据预处理等多个领域都有广泛应用。

2 步骤

  1. 初始化一个大小为k的空样本集,称为蓄水池(reservoir)。
  2. 对于输入序列中的每个元素x,执行以下操作: a. 如果蓄水池尚未满(即蓄水池中的元素数量小于k),则将x添加到蓄水池中。 b. 如果蓄水池已满(即蓄水池中的元素数量等于k),则以k/n的概率保留x,其中n是当前已经处理过的元素数量(包括x)。如果保留x,则从蓄水池中随机移除一个元素,并将x添加到蓄水池中。
  3. 继续处理输入序列中的下一个元素,直到处理完所有元素。
  4. 最后,蓄水池中的元素就是从输入序列中随机选择的k个样本。

3 原理解释

3.1 P(1) ~ P(5) 

由于1~5一开始就在采样池中,P(1)~P(5) 概率算法一样,以1为例:

  1. 进入采样池中的概率为1。
  2. 被6替换的概率为1/6,被保留的概率就是5/6。
  3. 以此类推。

P(1) = 1* \frac{5}{6} * \frac{6}{7} * \frac{7}{8} * \frac{8}{9} * \frac{9}{10} = \frac{5}{10}

3.2 P(6) ~ P(10)

对于一开始不在采样池中的数,以6为例:

  1. 由于要生成0~6的随机数,要进入采样池,那概数就得小于5,故进入采样池的概率为5/6。
  2. 被后续数字7替换的概率为1/7,被保留的概率就是6/7。
  3. 以此类推。

P(6) = \frac{5}{6} * \frac{6}{7} * \frac{7}{8} * \frac{8}{9} * \frac{9}{10} = \frac{5}{10}

3.2 P(n)

P(i | i\in [1,m]) = 1* \frac{m}{m+1} * \frac{m+1}{m+2} * \frac{m+2}{m+3} * ...*\frac{n-1}{n} = \frac{m}{n}

P(i | i\in [m+1,n]) = \frac{m}{m+1} * \frac{m+1}{m+2} * \frac{m+2}{m+3} * ...*\frac{n-1}{n} = \frac{m}{n}

4 代码实现

import java.util.Random;

public class ReservoirSampling {
    private int[] reservoir;
    private Random random;

    public ReservoirSampling(int k) {
        reservoir = new int[k];
        random = new Random();
    }

    public void add(int value) {
        int i = random.nextInt(reservoir.length + 1);
        if (i < reservoir.length) {
            reservoir[i] = value;
        }
    }

    public int[] getSamples() {
        return reservoir;
    }

    public static void main(String[] args) {
        int k = 5; // 抽样数量
        int streamLength = 100; // 数据流长度
        ReservoirSampling reservoirSampling = new ReservoirSampling(k);

        // 模拟数据流
        for (int i = 0; i < streamLength; i++) {
            reservoirSampling.add(i);
        }

        // 输出抽样结果
        int[] samples = reservoirSampling.getSamples();
        System.out.print("抽样结果: ");
        for (int sample : samples) {
            System.out.print(sample + " ");
        }
    }
}

5 优缺点

5.1优点

  • 内存效率:蓄水池抽样算法不需要一次性将所有数据加载到内存中,而是逐个处理数据元素。这使得它非常适合于处理大型数据集或数据流,尤其是当内存有限时。

  • 简单性:算法实现简单,逻辑清晰,容易理解和实现。

  • 均匀分布:每个元素被选中的概率是相等的,即每个元素被选中的概率都是k/n,其中k是样本集的大小,n是输入序列的长度。这保证了抽样的代表性和公平性。

  • 在线处理能力:算法可以实时处理数据流,无需等待所有数据都可用,这对于需要快速响应的应用场景非常有用。

5.2 缺点

  • 替换策略:当蓄水池满后,每次新元素进入都可能导致旧元素的替换,这可能会引入一些额外的随机性,尤其是在样本集大小固定且较小的情况下。

  • 初始样本可能不具代表性:由于算法在初期阶段会直接将前k个元素放入蓄水池,如果这些元素不具代表性,可能会导致最终的样本集也不具代表性。

  • 适应性问题:算法一旦确定了样本集大小k,就不易于调整。如果需要动态改变样本集的大小,可能需要重新运行整个算法。

       

6 使用场景

  • 处理大型数据集,尤其是当内存有限时。
  • 数据流抽样,例如网络数据包分析等。
  • 任何需要从大量数据中随机抽取样本的场景。

7 注意事项

  • 确保随机数生成器的质量和正确性。
  • 考虑样本集大小的选择,以及如何根据应用场景调整大小。
  • 在实现时注意代码的优化,以提高处理速度和效率。

8 练习

398. 随机数索引

给你一个可能含有 重复元素 的整数数组 nums ,请你随机输出给定的目标数字 target 的索引。你可以假设给定的数字一定存在于数组中。

实现 Solution 类:

  • Solution(int[] nums) 用数组 nums 初始化对象。
  • int pick(int target) 从 nums 中选出一个满足 nums[i] == target 的随机索引 i 。如果存在多个有效的索引,则每个索引的返回概率应当相等。

示例:

输入
["Solution", "pick", "pick", "pick"]
[[[1, 2, 3, 3, 3]], [3], [1], [3]]
输出
[null, 4, 0, 2]

解释
Solution solution = new Solution([1, 2, 3, 3, 3]);
solution.pick(3); // 随机返回索引 2, 3 或者 4 之一。每个索引的返回概率应该相等。
solution.pick(1); // 返回 0 。因为只有 nums[0] 等于 1 。
solution.pick(3); // 随机返回索引 2, 3 或者 4 之一。每个索引的返回概率应该相等。

提示:

  • 1 <= nums.length <= 2 * 104
  • -231 <= nums[i] <= 231 - 1
  • target 是 nums 中的一个整数
  • 最多调用 pick 函数 104 次
class Solution {
    Map<Integer, List<Integer>> map = new HashMap<>();
    public Solution(int[] nums) {
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            List<Integer> list = map.getOrDefault(nums[i], new ArrayList<>());
            list.add(i);
            map.put(nums[i], list);
        }
    }
    
    public int pick(int target) {
        Random random = new Random();
        List<Integer> list = map.get(target);
        return list.get(random.nextInt(list.size()));
    }
}

9 总结

        总的来说,蓄水池抽样算法是一种非常实用的随机抽样技术,尤其适用于资源受限和数据量大的场景。尽管存在一些局限性,但它的优点通常远远超过其缺点,使其成为处理大规模数据集时的一个很好的选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值