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