题目要求
Given the radius and x-y positions of the center of a circle, write a function randPoint which generates a uniform random point in the circle.
Note:
1. input and output values are in floating-point.
2. radius and x-y position of the center of the circle is passed into the class constructor.
3. a point on the circumference of the circle is considered to be in the circle.
4. randPoint returns a size 2 array containing x-position and y-position of the random point, in that order.
Example 1:
Input:
["Solution","randPoint","randPoint","randPoint"]
[[1,0,0],[],[],[]]
Output: [null,[-0.72939,-0.65505],[-0.78502,-0.28626],[-0.83119,-0.19803]]
Example 2:
Input:
["Solution","randPoint","randPoint","randPoint"]
[[10,5,-7.5],[],[],[]]
Output: [null,[11.52438,-8.33273],[2.46992,-16.21705],[11.13430,-12.42337]]
Explanation of Input Syntax:
The input is two lists: the subroutines called and their arguments. Solution's constructor has three arguments, the radius, x-position of the center, and y-position of the center of the circle. randPoint has no arguments. Arguments are always wrapped with a list, even if there aren't any.
假设现在已知圆的圆心的 x 和 y 坐标,以及该圆的半径 radius。要求写一个随机点生成器,要求该生成器生成的点必须在圆内,且每一个点被生成的概率为相等的。规定圆周上的点也属于圆内。
思路 1:Rejection Sampling
该思路很简单,即取能够容下该圆的最小正方形,并且随机生成该正方形内的点。如果点不在圆内,则继续重新生成。正方形内等概率的随机点很好生成,可以直接利用 JAVA 内置的随机数生成器即可。x 坐标的随机数范围为[x-radius, x+radius], y 坐标的随机数范围为[y-radius, y+radius]。代码如下:
public double[] randPoint2() {
double x0 = x_center - radius;
double y0 = y_center - radius;
while(true) {double xg = x0 + Math.random() * radius * 2;
double yg = y0 + Math.random() * radius * 2;
if (Math.pow((xg - x_center) , 2) + Math.pow((yg - y_center), 2) <= radius * radius)
return new double[]{xg, yg};
}
}
思路二:极坐标
假如我们能够利用极坐标的思想,先从 (0, 360] 度之间生成一个随机的角度,再在 [0, radius] 之间生成一个随机的长度,理论上就可以了。但是通过这种方式生成的随机点会明显的在靠近圆心的位置密度更大,如下图所示(图片来源于网络):
究其原因在于,从圆心向圆周方向的半径扩展出去,单位长度所构成的圆环的面积并非相等的。如下图(图片来源于网络):
假设将圆周拆分为等分的 3 部分,则最内圈的面积为,中圈的面积为=3A,同理外圈的面积为 5A。如果按照半径上的每一个点都是等概率生成的话,会发现内圈因为面积更小,导致点的密度更大。但是由此也可以看出,第 i 圈的面积和单位面积成线性正相关。
接着要先介绍两个概率论中的概念:概率密度函数(probability density function 简称 pdf)和 累计分布函数(Cumulative Distribution Function 简称 cdf)。
概率密度函数 是指某个随机数生成器生成的数字在某个点附近的概率值。比如现在有一个随机数生成器能够等概率生成 [a,b] 区间的任意一个数,则其pdf(x)= 1/(b-a)
累计分布函数 对离散变量而言,所有小于等于 a 的值出现概率的和。还是以 [a,b] 区间的等概率随机数生成器为例,cdf(x)表示随机数生成器生成的数位于 [a,x] 区间内的概率,其值为cdf(x)=(x-a)/(b-a) a<=x<=b
可以看到 cdf 其实是对 pdf 在该区间上进行微积分计算的结果。
从这题的角度而言,既然已知随着 r 向着半径增加,在该位置上生成随机数的概率为线性增加,因此可以设 ,其中 a 为概率值。因为已知生成的所有点的必定位于[0,R] 之上,因此 cdf(R)=,由此可以得出 。再将 a 的值带回原式中,可以得出。在对 pdf(r) 进行积分计算可以得出。再次校验可以看到 cdf(R)= 1 确实成立。
但是,JAVA 只能提供一种通用的随机数生成器,即在一定区间内每一个数均为等概率出现。对于这种随机数生成器,其 cdf(x)=x
,即生成[0,x] 之间的随机数的概率为 r。将二者相等即可得出如下结论。
代码如下:
public double[] randPoint() {double len= Math.sqrt(Math.random())*radius;
double deg= Math.random()*2*Math.PI;
double x= x_center+len*Math.cos(deg);
double y= y_center+len*Math.sin(deg);
return new double[]{x,y};
}