假设函数f()等概率随机返回一个在[0,1)范围上的浮点数,那么我们知道,在[0,x)区间上的数出现的概率为x(0
class RandomSeg {
public:
// 等概率返回[0,1)
double f() {
return rand() * 1.0 / RAND_MAX;
}
// 通过调用f()来实现
double random(int k, double x) {
if(k<1)
return 0;
vector<double> vec;
while(k--)
vec.push_back(f());
double max=-1.0;
for(auto c:vec)
{
if(c>max)
max=c;
}
return max;
}
};
给定一个长度为N且没有重复元素的数组arr和一个整数M,实现函数等概率随机打印arr中的M个数。
每次随机选择一个序号,之后将这个序号的数放到最后一个,再从前面随机选一个。
class RandomPrint {
public:
vector<int> print(vector<int> arr, int N, int M) {
vector<int> res;
for(int i=0;i!=M;++i){
int num = rand()%(N-i);
res.push_back(arr[num]);
swap(arr[num],arr[N-1-i]);
}
return res;
}
void swap(int &a,int &b)
{
int temp;
temp=a;
a=b;
b=temp;
}
};
有一个机器按自然数序列的方式吐出球,1号球,2号球,3号球等等。你有一个袋子,袋子里最多只能装下K个球,并且除袋子以外,你没有更多的空间,一个球一旦扔掉,就再也不可拿回。设计一种选择方式,使得当机器吐出第N号球的时候,你袋子中的球数是K个,同时可以保证从1号球到N号球中的每一个,被选进袋子的概率都是K/N。举一个更具体的例子,有一个只能装下10个球的袋子,当吐出100个球时,袋子里有10 球,并且1~100号中的每一个球被选中的概率都是10/100。然后继续吐球,当吐出1000个球时,袋子里有 10 个球,并且1~1000号中的每一个球被选中的概率都是10/1000。继续吐球,当吐出i个球时,袋子里有10个球,并且1~i号中的每一个球被选中的概率都是10/i。也就是随着N的变化,1~N号球被选中的概率动态变化成k/N。请将吐出第N个球时袋子中的球的编号返回。
这个题目是经典的蓄水池算法,有很多的应用,比如在不知道总量的情况下随机选取某一行文档等。
1、处理1~k号球时,直接放进袋子里
2、处理第i号球时,以k/i的概率决定是否将第i号球放进袋子,如果不决定将第i号球放进袋子,直接扔掉第i号球,否者将袋子里的球随机扔掉一个,将i号球放入袋子
3、处理第i+1号球时,重复步骤1和步骤2.
证明过程:
假设第i号球被选中,并且i大于1小于k,在选k+1号球之前,第i号球留在袋子中的概率为1,在选k+1号球时,只有决定将k+1号球放入袋子,同时在袋子中的第i号球被随机选中并决定仍掉,这两个事件同时发生时第i号球才会被淘汰,第i号球被淘汰的概率是(k/(k+1))*(1/k)等于1/(k+1)所以第i号球留下来的概率是是k/(k+1);
依次类推,在选N号球时,从1号到N号的全部过程中,第i号球最终留在袋子中的概率是打搅看到的式子,化简之后为k/N。
当i>k时,再选i号球时,i号球选进袋子中的概率是k/i;在选第i+1号球时,只有决定将i+1放入袋子,并且决定扔掉i号球时,i号球才被淘汰,概率为(k/(i+1))(1/k),所以i号球留下来的概率是i/(i+1).从i号球被选中到第i+1号球的过程中,i号球留在袋子中的概率是(i/k)(i/(i+1).
和上面的分析过程类似,每一次处理球的过程中,第i号球都会有被淘汰的可能,当然也能算出被留下来的概率,当处理完N号球的时候,第i号球仍然没有被淘汰的概率依然为k/N。所以方法有效
代码如下:
class Bag {
public:
vector<int> ret;
// 每次拿一个球都会调用这个函数,N表示第i次调用
vector<int> carryBalls(int N, int k) {
if(N<=k)
ret.push_back(N);
else{
double a=rand()%N;
if(a<k)
{
ret[a]=N;
}
}
return ret;
}
};