蓄水池抽樣算法(Reservoir Sampling)

問題:

給定很多很多很多的N個數據,如何在只遍歷一次的情況下隨機選出m個不重複的數據?

這個場景強調了3件事:

  1. 數據流長度N很大且不可知,所以不能一次性存入內存。
  2. 時間複雜度爲O(N)。
  3. 隨機選取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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章