水塘抽样算法(Reservoir Sampling Algorithm),其目的在于从包含n个项目的集合S中选取k个样本,其中n为一很大或未知的数量,尤其适用于不能把所有n个项目都存放到内存的情况。
算法步骤
在长度为n的序列流中随机取k个元素(n未知)
步骤1. 对于前k个元素,一定会留下来
步骤2. 对于第i(k<i<=n)个元素,k/i的几率留下来,留下来的话,已经留下来的k个元素要随机出一个剔除掉,则每个元素有1/k的几率被剔除
算法分析
当k>=n,每个元素被留下的概率都为1,等概率
当k<n,现在我们求第j个元素被留下的几率
1.若j<=k,则从读取第k+1个元素开始,每次被留下的几率为1/(i-1)(i为正在读取的元素个数)
所以第j个元素在读取第i个元素时留下的几率为
最后第j个元素被留下的概率为
2.若j>k,则被留下的概率为
综上所述,每个元素被留下的几率都是k/n
算法实现
public int[] reservoirSampling(int[] nums, int k) {
int[] reservoir = new int[k];
Random random = new Random();
// 填充初始的水塘
for (int i = 0; i < k ; i++) {
reservoir[i] = nums[i];
}
// 使用水塘抽样算法更新水塘中的元素
for (int i = k; i < nums.length; i++) {
int j = random.nextInt(i + 1); // 生成一个 0 到 i 的随机索引
if (j < k) { // 如果随机索引小于 k,则替换水塘中的元素
reservoir[j] = nums[i];
}
}
return reservoir;
}