问题:
给定很多很多很多的N个数据,如何在只遍历一次的情况下随机选出m个不重复的数据?
这个场景强调了3件事:
- 数据流长度N很大且不可知,所以不能一次性存入内存。
- 时间复杂度为O(N)。
- 随机选取m个数,每个数被选中的概率为m/N。
第1点限制了不能直接取N内的m个随机数,然后按索引取出数据。
第2点限制了不能先遍历一遍,然后分块存储数据,再随机选取。
第3点是数据选取绝对随机的保证。
蓄水池抽样算法
先取前m个数放入蓄水池中。从m+1开始,以m/(m+1)的概率选择该对象,以m/(m+2)的概率选择第m+2个对象,以此类推。。。以m/(m+k)的概率选择第k个对象。如果被选中,则随机替换水池中的一个对象。最终每个对象被选中的概率均为m/N。
证明如下:
#include <bits/stdc++.h>
using namespace std;
typedef vector<int> IntVec;
typedef typename IntVec::iterator Iter;
typedef typename IntVec::const_iterator Const_Iter;
int randint(int i, int k)
{
if (i > k)
{
int t = i;
i = k;
k = t;
}
return i + rand() % (k - i + 1);
}
bool reservoir_sampling(const IntVec& input, IntVec& result, int m)
{
srand(time(NULL));
if (input.size() < m)return false;
result.resize(m);
Const_Iter iter = input.begin();
for (int i = 0; i != m; i++)result[i] = *iter++;
for (int i = m; iter != input.end(); i++, iter++)
{
int j = randint(0, i);
if (j < m)result[j] = *iter;
}
return true;
}
int main(void)
{
const int n = 100, m = 10;
IntVec input(n), result(m);
for (int i = 0; i != n; i++)input[i] = i;
if (reservoir_sampling(input, result, m))for (int i = 0; i != m; ++i)cout << result[i] << " ";
cout << endl;
}