算法面试_蓄水池抽样算法系列问题(Revervoir Sampling)

题目1:如何随机从n个数字中选择一个数字,这n个数字是按序排列的,但是在此之前不知道n的值。

思路:如果我们知道n的值,那么问题就可以简单的用一个大随机数rand()%n来得到一个确切的随机位置,那么该位置的数字就是所求的数字,选中的概率为1/n。

但是现在我们并不知道n的值,这个问题便抽象为蓄水池抽样问题,即从一个包含n个数字的数组numbers中随机选取k个数字,n为一个非常大或者不知道的值。通常情况下,n是一个非常大的值,大到无法一次性把所有数组numbers中的数字都放到内存中。我们这个问题是蓄水池抽样问题的一个特例,即使k=1。

解法:我们总是选择第一个数字,以1/2的概率选择第二个,以1/3的概率选择第三个,以此类推,以1/m的概率选择第m个数字。当该过程结束时,每一个数字具有相同的选中概率,即1/n。

下面我们进行证明:第m个数字最终被选中的概率P=选择m的概率 * 其后面所有数字不被选择的概率如下面的公式所示:

在这里插入图片描述

接下来我们用C++代码编程:

typedef vector<int> IntVec;
typedef typename IntVec::iterator Iter;
typedef typename IntVec::const_iterator Const_Iter;

//产生一个在i和k之间,包括i和k。
int randint(int i, int k)
{
	if(i > k)
	{
		int t =i; i = k; k = t;
	}
	int ret = i + rand() % (k - i + 1);
	return ret;
}

// 从不知道大小的n个数字中取一个数。
bool reservoir_sampling(const IntVec &input, int& result)
{
	if(input.size() <= 0)
		return false;

	Const_Iter iter = input.begin();
	result = *iter++;
	for(int i = 1; iter != input.end(); ++iter, ++i)
	{
		int j = randint(0, i);
		if(j < 1)
			result = *iter;
	}
	return true;
}

问题2:对应蓄水池抽样问题,可以用类似的思路解决问题。先把读到的前K个数字放入“水库”,对于第k + 1个数字开始,以k/(k+1)的概率选择该数字,以k/(k+2)的概率选择第k+2个数字,以此类推,以k/m的概率选择第m个数字(m > k)。如果m被选中,则随机替换水库中的一个数字。最终每个数字被选中的概率均为k/n。

接下来我们进行证明第m个数字被选中的概率 = 选择m的概率 * (其后数字不被选择的概率 + 其后数字被选择的概率 * 不替换第m个数字的概率),如下面的公式所示:

在这里插入图片描述

接下来我们用C++代码来实现,该版本假设n已知,但n非常大:

int randint(int i, int k)
{
	if(i > k)
	{	
		int t = i; i = k; k = t;
	}
	int ret = i + rand() % (k - i + 1);
	return ret;
}

bool reservoir_sampling(const int* input, int n, int* result, int m)
{
	if(n < m || input == nullptr || result == nullptr)
		return false;
	for(int i = 0; i != m; ++i)
		result[i] = input[i];

	for(int i = m; i != n; ++i)
	{
		int j = randint(0,i);
		if(j < m)
			result[j] = input[i];
	}
	return true;
}

下面我们再用C++编写一段代码,这个时候我们假设n未知:

int randint(int i, int k)
{
	if (i > k)
	{
		int t = i; i = k; k = t; // swap
	}
	int ret = i + rand() % (k - i + 1);
	return ret;
}

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;
}

发布了94 篇原创文章 · 获赞 37 · 访问量 3444

猜你喜欢

转载自blog.csdn.net/Rocky6688/article/details/103527822