什么是储蓄池抽样(Reservoir Sampling)算法?
首先 我们要知道这种算法到底是干嘛的?
它通常用于解决大数据流中的数据的随机抽样问题。进一步简化来说 当内存无法加载全部数据的时候 如何从包含位置大小的数据流中随机选取K个数据,并且要保证每个数据被抽取到的概率相等。
首先,针对不同的K 我们需要有不同的策略:
当K=1的时候
我们需要保证 假设数据流含有N个数 每个数被抽到的概率应该为1/N
应该这样做:
数据流中第i个数被保留的概率为 1/i 。只要采取这种策略,只需要遍历一遍数据流就可以得到采样值,并且保证所有数被选取的概率均为 1/N 。
为什么这种策略可以?
记住 我们每次最后只保留一个数
遇到第一个数的时候 我们现在只有一个数 保留他的概率100%
遇到第二个数 我们有1/2的概率保留(也就是说替换为)第二个数 这也就意味着有11/2的概率保留第一个数 两者概率均为1/2
遇到第三个数 我们有1/3的概率保留第三个数,那么我们留下第一个数的概率为1/2(1-1/3) 留下第二个数的概率也为1/2*(1-1/3)
…所以就按照这种策略 我们能保证所有被选数的抽取概率都是1/N
当K>1的时候
我们需要保证 假设数据流含有N个数 每个数被抽到的概率应该为K/N.
那么我们应该这样做:
对于前k个数,我们全部保留,对于第i(i>k)个数,我们以 [公式] 的概率保留第i个数,并以 [公式] 的概率与前面已选择的k个数中的任意一个替换。
为什么这种策略可以?
就跟之前一样 对于前K个数 我们一定是全部都保留下来
对于第K+1个数 我们以K/(K+1)的概率保留他(但是替换谁呢?)
对于第K+2个数 我们以K/(K+2)的概率保留她
…
至于为什么 如何去证明,请参见:
水塘抽样(Reservoir Sampling)
下面是这种抽样方法的具体实现:
//if k ==1
public int reserviorSampling(int[] nums) { //data stream
int N = nums.length;
int res = nums[0];
Random rand = new Random();
for (int i = 1; i < N; i++) {
int random = rand.nextInt() % i; //the range of this will be 0 to i-1,
if (random == 0) { //there is the possibility of 1/i
res = nums[i];
}
}
return res;
}
//if k > 1, then we need to use
public int[] reserviorSampling(int[] nums, int K) { //data stream
int N = nums.length;
int[] res = new int[K];
for (int i = 0; i < K; i++) {
res[i] = nums[i];
}
Random rand = new Random();
for (int i = 1; i < N; i++) {
int random = rand.nextInt() % i; //the range of this will be 0 to i-1,
if (random < K) { //the psosition of index=random will have the possibility of k/i change into this new value: nums[i]
res[random] = nums[i];
}
}
return res;
}

浙公网安备 33010602011771号