给定一个数据流,数据流长度N很大,且N直到处理完所有数据之前都不可知,从 N 个样本中随机选择 K 个样本,其中 N 非常大(以至于 N 个样本不能同时放入内存)或者 N 是一个未知数,请问如何在只遍历一遍数据(O(N))的情况下,能够随机选取出k个不重复的数据:
假设数据序列的规模为 n,需要采样的数量的为 k。
首先构建一个可容纳 k 个元素的数组,将序列的前 k 个元素放入数组中。然后从第 k+1 个元素开始迭代直到数组结束,在输入数据S的第 i 个元素中,算法生成一个随机数 j∈[1,i] , 如果 j <= k, 那么蓄水池的第 j 个元素以 k/n 的概率被替换为 S中的第 i 个元素。当遍历完所有元素之后,数组中剩下的元素即为所需采取的样本。
step 0: 假设要抽取K个样本,准备一个K个元素的数组A
step 1: 把输入数据的前K个数放入数组A中;
step 2:遍历输入数据的第i=K+1, K+2,……个数:x
生成一个随机数 r,r的取值范围 [0, K + i),如果 r
step 3:遍历所有数据后,得到A,返回结果。
package cn.lens.test;
import java.util.Random;
/**
* @Author: Lens
* @Date: 2021/7/22 9:29
*/
public class ReservoirSampleTest {
public static void main(String[] args) {
int input_str[] = {11, 21, 23, 4, 15, 20, 83, 414, 75, 74, 82, 19, 50, 12, 13, 81, 22, 3, 44, 45, 56, 65, 44};
int[] arr_res = sample(input_str, 5);
for (int num : arr_res) {
System.out.println(num);
}
}
/**
*
* @param input 模拟原始数组
* @param k 采样的个数
* @return 返回采样的结果数据
*/
public static int[] sample(int[] input, int k) {
Random random = new Random();
int[] res = new int[k];
for (int i = 0; i < input.length; i++) {
if (i < k) {
res[i] = input[i]; //先取前k个数字放在数组里面
} else { //如果i > k,在 1 - i之间取一个随机数字,如果这个随机数字小于k,就替换数组,否则就继续遍历,直到结束
int rand = random.nextInt(i);
if (rand < k) {
res[rand] = input[i];
}
}
}
return res;
}
}
蓄水池抽样算法的O(N)时间复杂度,O(m)空间复杂度令其适用于对流数据、大数据集的等概率抽样。如一个大文本数据,随机输出其中的几行。