問題:
給定很多很多很多的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;
}