蓄水池抽样算法(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;
}
发布了175 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43461641/article/details/102923975