先说说什么是拒绝采样算法:就类似于数学上的求阴影面积的方法,直接求求不出来,就用大面积 - 小面积 = 阴影面积的办法。
所谓拒绝 和 采样 :就像是撒豆子计个数,计算概率问题一样,大桶里面套小桶,一把豆子撒下去,每个豆子都是一个“样本”,如果落在小桶外面的大桶里面去了,就“拒绝”这个样本,如果在小桶里,就“采用”这个样本, 就这样拒绝-和采用所有的豆子,小桶里面的豆子数量除以所有的豆子的数量就得到啊小桶在大桶里的占比,也就是豆子落在小桶里的概率……………………巴拉巴拉一些关于概率的问题就可以这样求解了。
这是力扣的两题,一一举例加以解释。
读题:现在有一个只能生成1、2、3、4、5、6、7这7个数字的随机函数Rand7(),问你如何用这个函数实现一个可以随机生成1~10的随机函数Rand10()(PS:随机函数,生成其中每个值的概率必须相等才行)
想法:想想二进制(011010010111000010101010)这玩意,用两个Rand7()就可以生成7*7=49种选择,我们只要10种就够了,所以可以有
1和1、1和2、1和3 表示 1
1和4、1和5、1和6 表示 2
2和1、2和2、2和3 表示 3
2和4、2和5、2和6 表示 4
3和1、3和2、3和3 表示 5
3和4、3和5、3和6 表示 6
4和1、4和2、4和3 表示 7
4和4、4和5、4和6 表示 8
5和1、5和2、5和3 表示 9
5和4、5和5、5和6 表示 10
6和1、6和2、6和3 (拒绝表示)
6和4、6和5、6和6 (拒绝表示)
7和1、7和2、7和3 (拒绝表示)
7和4、7和5、7和6 (拒绝表示)
也就是:第一个Rand7() 只能生成1~5中的一个数,第二个Rand7()只能生成1~6中的一个数,不然就拒绝采样,重新生成才行。
代码:(优化前)
class Solution {
public:
int rand10() {
int a,b;
while(1){
a = rand7();
if( a != 6 && a != 7 ) break;
}
while(1){
b = rand7();
if( b != 7 ) break;
}
if( a == 1 ){
if( b <= 3 ) return 1;
else return 2;
}
else if( a == 2 ){
if( b <= 3 ) return 3;
else return 4;
}
else if( a == 3 ){
if( b <= 3 ) return 5;
else return 6;
}
else if( a == 4 ){
if( b <= 3 ) return 7;
else return 8;
}
else {
if( b <= 3 ) return 9;
else return 10;
}
}
};
代码:(优化后)
class Solution {
public:
int rand10() {
while (true) {
int num = (rand7() - 1) * 7 + rand7();
if (num <= 40) return num % 10 + 1;
}
}
};
不懂??????????没关系,看第二个,更简单!!!!!!!!!!!!!!!!!
第二题(别看题目,看下面读题)
读题:给一个半径 r0 和圆心坐标 x0, y0 ; 然后返回这个圆上或者圆内随机一点的坐标值(注:全是double类型,而且落在每一点上的概率必须相等)
官解:两个随机函数呗,一个随机范围是 [ x0-r0, x0+r0 ] ,另一个是[ y0-r0, y0+r0 ], 只要两个随机数的平方和大于了半径 r0 就统统 “拒绝”,只计算 平方和小于半径的结果,看图:
(这官解太low了)
这不就是撒豆子算概率问题嘛,随机生成的坐标点 x , y 就豆子的落点,这个落点只能在圆内,如果在圆外了就“拒绝”这个坐标,给我重新生成去。
(单纯是为了说明拒绝采样算法而已,此题有更佳的解法)
// 作者:力扣官方题解
class Solution {
private:
mt19937 gen{random_device{}()};
uniform_real_distribution<double> dis;
double xc, yc, r;
public:
Solution(double radius, double x_center, double y_center): dis(-radius, radius), xc(x_center), yc(y_center), r(radius) {}
vector<double> randPoint() {
while (true) {
double x = dis(gen), y = dis(gen);
if (x * x + y * y <= r * r) {
return {xc + x, yc + y};
}
}
}
};
最有解法:(极坐标法)
(这字丑自己都不想看)
代码:(并没有用拒绝采样算法,但是效率上是它的两倍,拒绝采样要170ms+,但是极坐标只需要80ms)
class Solution {
private:
double rc,xc,yc;
public:
Solution(double radius, double x_center, double y_center) {
rc = radius;
xc = x_center;
yc = y_center;
}
vector<double> randPoint() {
double Rx = rc * sqrt( (double)rand()/RAND_MAX );
double angle = 2 * M_PI * (double)rand()/RAND_MAX;
return { xc + Rx*cos(angle), yc + Rx*sin(angle)};
}
};