「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战」。
前言
随机数是非常有用的工具,尤其是在模拟一些自然现象或者不确定的事件。比如在游戏开发中,我们可以通过随机数来模拟NPC的随机游走,还可以通过随机数产生一些随机的数据,比如模拟自然族群中不同特征的族群分布等。今天,我们就来学习一下随机数相关的知识。
随机游走
假设我们现在有一张无限大的地图,有一个NPC角色需要在地图上随机的游走。那么,我们可以通过随机数在控制这样的行为。我们通过产生一个随机数来确定NPC需要往哪个方西上进行移动,我们产生-1~1之间的随机数分别确定NPC在X和Y方向上移动一次的距离。
使用p5.js,我们能够写出以下简单的代码
function sketch() {
return {};
}
const p = new P5(sketch, document.body);
let walker: Walker = null;
p.setup = function () {
p.createCanvas(640, 360);
walker = new Walker(p, 320, 180);
};
p.draw = function () {
p.stroke(0);
walker.update();
walker.display();
};
class Walker {
x: number = 0;
y: number = 0;
constructor(private p: p5, x: number, y: number) {
this.x = x;
this.y = y;
}
display(): void {
this.p.stroke(0);
this.p.point(this.x, this.y);
}
update(): void {
const v = Vector.random2D();
this.x += v.x;
this.y += v.y;
}
}
程序运行的结果如下:
以上就是一个随机数应用的简单例子。上述NPC随机游走的例子中有一个特征:NPC在任意时刻朝某一方向运动的概率是相同的。对于在某一范围产生的随机数的概率均相等的随机数,我们称之为平均分布的随机数。
高斯分布
现在我们来到第二个应用场景,假设我们需要模拟某个国家的人口,我们用随机数来产生不同身高的人类。那么我们还使用这种平均分布的随机数明显不适用,根据调查,人们的身高并不是均匀分布的,大部分人的身高都处于平均身高的附近,特别高和特别矮的人总是少数。所以,这里采用平均分布的随机数并不是很合数。根据调查,人类身高的分布基本服从高斯分布的模型。
假设这样的一个场景,我们现在需要模拟各色的颜料飞溅在画板上的效果,这些飞溅的颜料点大部分都应该处于中间的位置。我们就可以采用高斯分布这一特性来进行模拟。
const points: DrawPoint[] = [];
p.setup = function () {
p.createCanvas(640, 360);
walker = new Walker(p, 320, 180);
};
p.draw = function () {
p.clear();
p.stroke(0);
points.push(new DrawPoint(p));
points.forEach((p) => p.display());
};
class DrawPoint {
x: number;
y: number;
color: number[];
constructor(private p: p5) {
this.x = p.randomGaussian(0.5, 0.2) * 640;
this.y = p.randomGaussian(0.5, 0.2) * 320;
this.color = [p.random(0, 255), p.random(0, 255), p.random(0, 255)];
}
display(): void {
this.p.fill(this.color);
this.p.circle(this.x, this.y, 5);
}
}
程序的运行结果如下,我们可以发现大部分的圆点都落在了画布靠近中心的位置,画布边缘落下的点明显要少很多。
自定义分布的随机数
但是有很多例子都无法直接的使用均匀分布与高斯分布来模拟。我们可能需要产生一个服从某个概率分布函数的随机数。
还是以上面颜料落在画布上的例子举例,我们现在不太想让这些点落下的位置服从正态分布了,假设现在我们想要点位服从指数分布。指数分布的概率密度函数如下:
我们可以观察到,越靠近0点的地方越容易被随机到,这意味着我们的颜料落下的位置应该更接近于左上角。那么我们应该如何产生服从自定义概率密度函数的的随机数呢?
这里介绍一种常见的方法,该方法名为 Accept-Reject 方法,该方法的核心思想在于:
- 选择一个随机数R1
- 选择另一个随机数R2
- 如果R2 小于
pdf(R1),则 R1 就是我们需要的随机数 - 反之则从头开始
算法描述如下:
function montecarlo(pdf: (x: number) => number): number {
while (true) {
const a = Math.random();
const b = Math.random() * 5;
if (b < pdf(a)) {
return a;
}
}
}
此处的pdf指的是概率密度函数 (probability density function),根据该函数我们可以得到服从任意概率密度分布函数的随机数了。那么我们也可以轻易的让我们的颜料点服从指数分布,结果如下:
注意:
这里需要特别说明一点,你是否有疑问这里b = Math.random() * 5为什么有一个5? 这个5表示的是概率密度函数的极大值。我们可以通过下面几张图来说明这里为什么需要乘以它的极大值。
如果我们不乘以PDF函数的极大值,我们使用该算法产生的随机数分布如下:
我们可以看到,由于我们产生的值最大就是0~1,但是pdf函数产生的值有一部分明显大于了1所以会有一部分大于1的值不能被表示出来。此时,我们需要手动的调整这个参数,此时,我们让 b = Math.random() * 2
这样,我们得到的点才是真正服从于高斯分布的。所以该算法的不足之处就是需要针对不同的概率密度函数去设置不同的极大值。
总结
今天我们学习了随机数的一些应用,利用随机数我们可以作出一些不可思议的事情。但是js自带的随机数是均匀分布的随机数,但是对于某些场景,均匀分布的随机数并不太适用。由此我们又学习到了高斯分布这一随机分布函数。高斯分布的随机函数可以帮助我们解决一部分的场景,但是其灵活度不太高。我们更希望的是得到一个能够产生服从任意概率密度函数的随机数,最终我们利用 Accept-Reject 方法来帮助我们实现了这一特性。
好了,今天的内容差不多就是这么多。如果你觉得本文有用的话,不要忘了给作者点个赞~