蓄水池抽样
从N个元素中随机的等概率的抽取m个元素,其中N无法确定(数据流的形式)。
- 数据流长度N很大且不可知,所以不能一次性存入内存。
- 时间复杂度为O(N)。
- 随机选取m个数,每个数被选中的概率为m/N。 ```java int[] reservoir = new int[m];
// init for (int i = 0; i < reservoir.length; i++) { reservoir[i] = dataStream[i]; }
for (int i = m; i < dataStream.length; i++) { // 随机获得一个[0, i]内的随机整数 int d = rand.nextInt(i + 1); // 如果随机整数落在[0, m-1]范围内,则替换蓄水池中的元素 if (d < m) { reservoir[d] = dataStream[i]; } } ```
- 算法思路:
- 如果接收的数据量小于m,则依次放入蓄水池。
- 当接收到第i个数据时,i >= m,在[0, i]范围内取以随机数d,若d的落在[0, m-1]范围内,则用接收到的第i个数据替换蓄水池中的第d个数据。
- 重复步骤2。
算法的精妙之处在于:当处理完所有的数据时,蓄水池中的每个数据都是以m/N的概率获得的。
- 证明:
- 当i <= m时,将其放入蓄水池
- 当i>m的时候,从[1, i]中取随机数d,如果d<=m,则用第i个数代替第d个数据
- 当i>m的时候,以m+1为例,m+1,会以(m/m+1)的概率进入蓄水池,第m+1次处理替换掉小于等于m的某个数的概率是(m/(m+1)) x (1/m)=(1/(m+1)),不替换小于等于m的某个数的概率是(m/m+1)(即1-1/(m+1) ),第m+2个数不替换掉池中某个数的概率为((m+1)/(m+2))…..第N个数不替换掉池中某个数的概率为((N-1)/N)
- i<=m的这些数一直留在蓄水池的概率是(m/m+1) x (m+1/m+2) … x (N-1/N) = m/N。(即从第m+1个数开始,一直到第N个数,都不替换池中的数)。
- 对于i>m的这些数进入蓄水池的概率是m/i,而之后一直留在蓄水池的概率是(m/i) x (i/i+1) … x (N-1/N) = m/N。
- 进入蓄水池的概率即随机数d落到小于等于m的区间。一直留在蓄水池的概率即从第i+1个数开始一直到N,都不会替换已经进入池中的i。为什么从第i+1数开始,因为i>m时,后面的数才可能替换i。
- 下面是辅助理解的。蓄水池抽样