引子
一把武器的品质有分为最下级、下级、中级、上级、最上级五档,我希望在击杀一只怪物的时候,这把武器的掉率:最下级>下级>中级>上级>最上级,问如何设计满足上面的需求?
在游戏设计的过程中,经常会碰到需要设计很多不同的概率分布来达到一个系统的设计目的,让玩家在体验这个系统的时候感受更好,所以这时候均匀分布随机就不够用了,就需要开始为游戏设计不同的概率分布随机数。这里,介绍两种方法用来生成任意概率分布的随机数:逆变换方法、舍取方法。这两种算法其原理已被证实,所以这里不再进行论证,如果需要去探究其原理,可以去中国知网上搜索相关论文进行查看。
逆变换方法(Inverse Transform Method,ITM)
最简单的生成算法是Inverse Transform Method(下文简称ITM)。如果我们可以给出概率分布的累积分布函数(下文简称CDF)及其逆函数的解析表达式,则可以非常简单便捷的生成指定分布随机数。
-
ITM算法思路
- 根据给出的概率密度函数f(X)(PDF)积分得到其累积分布函数F(X)(CDF)
- 生成一个服从均匀分布的随机数S~U(0,1)
- 根据累积分布函数Y=F(X),求得其反函数X=F-1(Y)
- 返回X=F-1(S)作为本次生成的随机数
这种算法在实现时必须保证给出的概率密度函数,能够求出其累积分布函数CDF的逆函数,否则将无法求得所需要的随机数。下面就以指数分布为例,实现生成符合指数分布的随机数。
-
ITM方法的VBA实现
这里,先给出指数分布的PDF和CDF,λ是分布的一个参数,常被称为率参数(rate parameter)且λ>0。
概率密度函数
累积分布函数
1 Option Explicit 2 Option Base 1 3 4 Sub expDistribution() 5 Range("J:J").ClearContents 6 Range("J1") = "指数分布随机数" 7 8 Dim src As Single 'src:用来生成均匀分布随机数U(0,1) 9 Dim λ As Single 'λ:指数分布的参数 10 Dim num As Integer 'num:生成的随机数个数 11 Dim arr() As Integer 'arr():用来保存生成的随机数结果 12 13 λ = Range("C10").value 14 num = Range("C11").value 15 16 ReDim arr(1 To num) As Integer 17 Dim i As Integer 18 For i = 1 To num Step 1 19 src = Rnd() 20 arr(i) = WorksheetFunction.RoundDown(-1 / λ * Log(src), 0) 21 Next 22 23 For i = 1 To num Step 1 24 Range("J" & i + 1) = arr(i) 25 Next 26 End Sub
-
ITM实现结果
上述代码中可以看到,我将自己设定的30000个随机数(参数λ=0.3)都打印在表中J列单元格,这里给出最后的统计数据,如下。
可以看到,最终生成的30000个随机数,其数值的个数分布是符合之前给定的指数分布的。这种算法较为简洁高效,在能够使用的情况下,可以考虑优先使用该算法。
舍取方法(Acceptance-Rejection Method,ARM)
当无法给出CDF逆函数的解析表达式时,Acceptance-Rejection Method(下文简称ARM)是另外的选择。ARM的适用范围比ITM要大,只要给出概率密度函数(下文简称PDF)的解析表达式即可,而大多数常用分布的PDF是可以查到的。
-
ARM算法思路
- 设定我们需要实现的概率密度分布函数PDF为f(x),确定f(x)的定义域[xmin,xmax]、值域[ymin,ymax]
- 生成一个均匀分布随机数X~U(xmin,xmax)
- 生成一个均匀分布随机数Y~U(ymin,ymax)
- 如果Y≤f(X),则返回X作为本次的随机数;否则回到第2步
下面,依旧以指数分布为例,利用ARM实现该分布的随机数生成。
-
ARM方法的VBA实现
1 Option Explicit 2 Option Base 1 3 4 5 6 Sub ARMexpDistribution() 7 Range("J:J").ClearContents 8 Range("J1") = "指数分布随机数" 9 10 Dim src As Single 'src:用来生成第一个均匀分布随机数src~U(0,1 / λ * Log(λ / 0.0001)) 11 Dim dst As Single 'dst:用来生成第二个均匀分布随机数dst~U(0,λ) 12 Dim λ As Single 'λ:指数分布的参数 13 Dim num As Integer 'num:生成的随机数个数 14 Dim arr() As Integer 'arr():用来保存生成的随机数结果 15 16 λ = Range("C11").value 17 num = Range("C12").value 18 19 ReDim arr(1 To num) As Integer 20 Dim i As Integer 21 For i = 1 To num Step 1 22 Do 23 src = Rnd() * 1 / λ * Log(λ / 0.0001) 24 dst = Rnd() * λ 25 Loop While dst > λ * Exp(-λ * src) 26 arr(i) = WorksheetFunction.RoundDown(src, 0) 27 Next 28 29 For i = 1 To num Step 1 30 Range("J" & i + 1) = arr(i) 31 Next 32 End Sub
-
ARM实现结果
同样是生成30000个随机数,同样是参数λ=0.3,可以看到下面的列表中的统计数据以及统计图和第一种方法的实现结果是想吻合的,验证了这个方法的可行性。ARM本质上是一种模拟方法,而非直接数学方法。它每次生成新的随机数后,通过另一个随机数来保证其被接受概率服从指定的PDF。显然ARM从效率上不如ITM,但是其适应性更广,在无法得到CDF的逆函数时,ARM是不错的选择。